Software Architecture
This page is intended to provide an overview of the design, architecture and structure of the Tinman 3D SDK and to familiarize developers with the functionality it provides.
By convention, interface names always start with the prefix character I, for example ISomeInterface or IOtherInterface.
In the documentation, interface names are used as if the prefix character was not present: "This is a sentence with a ISomeInterface object…" "This is a sentence with an IOtherInterface object…" |
Code Concepts
This section describes the concepts which can be found in the source code. These concepts represent the foundation of the Tinman 3D SDK.
DEBUG vs. RELEASE
The binaries of the Tinman 3D SDK have two variants:
-
DEBUG
Code optimization was disabled for the build.
Runtime checks are enabled. -
RELEASE
Code optimization was enabled for the build.
Runtime checks are disabled.
During initialization, a log message is output which indicates whether DEBUG
or RELEASE
mode is used.
DEBUG
/ RELEASE
mode2024-03-15 20:58:09 [D] Using DEBUG binaries. [Tinman.Core.TinmanModule] ... 2024-03-15 20:58:19 [D] Using RELEASE binaries. [Tinman.Core.TinmanModule]
The following basic logging configuration can be used to enable output of the log messages.
DEBUG
/ RELEASE
modeLoggingUtil.BasicConfiguration(LogVerbosity.Info, LogVerbosity.Debug); (1)
TinmanModule.Logger.ChangeVerbosity(LogVerbosity.Debug); (2)
1 | Chooses Info as root log verbosity and Debug for the console appender. |
2 | Enables Debug verbosity for TinmanModule.Logger, which outputs the DEBUG / RELEASE messages. |
Distribution licence keys should not be used in these cases:
-
One or more
DEBUG
binaries are used, see DebugFlags.DebugBinary:
⇒ The distribution licence key will not unlock the Tinman 3D SDK. -
A debugger is attached to the process, see DebugFlags.DebugAttached:
⇒ To get support, an error report with DEBUG binaries might be required.
A Development licence key is required in such a non-RELEASE
case.
Debug Scenarios |
Development Key |
Distribution Key |
---|---|---|
|
yes |
no [1] |
Debugger active? |
yes |
yes [2] |
|
yes |
yes |
Due to the variety of project environments in which the Tinman 3D SDK may be used, it does not provide a global switch for choosing between the DEBUG
and RELEASE
binaries.
Instead, one of the following mechanisms may be used:
- NuGet Packages
-
The NuGet packages[3] contain two sets of binaries: one for
DEBUG
and one forRELEASE
. The custom MSBuild property$(TinmanBinariesNuget)
may be used to choose between the binary sets.MSBuildDEBUG
/RELEASE
choice<Project> <PropertyGroup> <TinmanBinariesNuget>DEBUG</TinmanBinariesNuget> (1) <TinmanBinariesNuget>RELEASE</TinmanBinariesNuget> (2) </PropertyGroup> </Project>
1 | Force use of DEBUG binary set. |
2 | Force use of RELEASE binary set.
If the
|
- sdk://src.cs/
-
The MSBuild files of the C# solutions projects in this directory use
<ProjectReference/>
to reference other solution projects and<Reference/>
to reference the library binaries in lib/win.any/:MSBuild references for C#<Project> <ItemGroup> <Reference Include="CodeX.System"> (1) <HintPath>...$(Configuration)/CodeX.System.dll</HintPath> </Reference> <ProjectReference Include=".../Tinman.AddOns.csproj"/> (2) </ItemGroup> </Project>
1 | The configuration name is used to automatically choose the directory that contains the correct binaries. |
2 | Another solution project is referenced, which implicitly chooses the correct binaries. |
- sdk://ide.cpp/vs/
-
The MSBuild files of the C++ solution projects in this directory use
<ProjectReference/>
items to reference other solutions projects, which implicitly chooses the correct binaries.The Tinman 3D SDK contains obfuscated C++ source code instead of pre-built static/dynamic link libraries (see Components), which allows to fine-tune the build settings and to generate tailor-made link libraries.
Mixing DEBUG
and RELEASE
binaries should be avoided.
Doing so can cause compilation and/or runtime errors.
Even if this is not the case, it will most likely lead to confusing or even deceptive behaviour at runtime, which complicates error analysis:
-
The developer assumes that runtime checks are in place, receives unexpected exceptions for
RELEASE
binaries and thus concludes that there is a difficult code bug somewhere, although the problem and solution would be apparent with theDEBUG
binaries.Before filing a bug report, please reproduce the problem with DEBUG
binaries, if applicable. -
A single
DEBUG
binary is sufficient to trigger DebugFlags.DebugBinary, which in turn may trigger additional debugging helpers, for example runtime validations for GPU rendering. This can reduce performance significantly, see Performance Guide for details.
A log message is output during initialization if binaries are mixed.
2024-03-15 21:00:04 [W] Using mixed DEBUG / RELEASE binaries: - Tinman.Core v1.0 : DEBUG - Tinman.Terrain v1.0 : RELEASE (1) - Tinman.Engine v1.0 : DEBUG - Tinman.AddOns v1.0 : DEBUG - Tinman.AddOns.DirectX11 v1.0 : DEBUG - Tinman.AddOns.DirectX12 v1.0 : DEBUG - Tinman.AddOns.DirectX9 v1.0 : DEBUG - Tinman.AddOns.Vulkan v1.0 : DEBUG - Tinman.Demo v1.0 : DEBUG - Tinman.Demo.StandAlone v1.0 : DEBUG [Tinman.Core.TinmanModule]
1 | A binary with mismatching mode |
Initialization / Shutdown
The Tinman 3D SDK must be initialized before it can be used by an application. When it is longer being used, it may be shut down. It is possible to re-initialize the SDK after it has been shut down.
The SDK is composed of individual modules, which may have dependencies on each other. Their dependency graph is taken into account by the initialization and shut down process.
During initialization, the following steps are performed:
-
Licence checking
The SDK is unlocked using one of the specified licence keys. -
Global object initialization
The computing environment will initialize objects in the global scope lazily upon first use. To ensure deterministic behaviour, lazy initialization is enforced with IPleaseIncludeInBinaryThanks. -
Module-specific initialization
A module may need to perform custom initialization work, for example checking if a graphics API is available.
The code below will initialize the Tinman 3D SDK, using the top-level module TinmanEngineModule.
In most cases, a single top-level module is sufficient, because of the module’s dependencies. For example, TinmanAddOnsDirectX11Module would be sufficient for a Direct3D 11 application. The Demo Application uses its own module. |
using Tinman.Core;
using Tinman.Core.Licensing;
using Tinman.Engine;
...
LicenceDomain.Tinman.LicenceKey("..."); (1)
TinmanEngineModule.Instance.PleaseIncludeInBinaryThanks(); (2)
TinmanModule.Initialize(); (3)
1 | Set the licence key. |
2 | Force lazy initialization. |
3 | Initialize the SDK modules. |
The code below will shut down the Tinman 3D SDK.
using Tinman.Core;
...
TinmanModule.Shutdown(); (1)
1 | Shutdown the Tinman 3D SDK. |
Objects in the global scope that are affected by initialization and shut down are annotated with the ShutdownClearAttribute and ShutdownSurviveAttribute attributes, which depict how these objects are treated during shut down. |
Disposal and Ownership
The life-time of all dynamically allocated objects is governed by the computing environment and cannot be controlled directly with the API, i.e. objects cannot be deleted explicitly.
For C#, the garbage collector of the .NET implementation takes care of object life-time, whereas for C++, this is achieved by using reference-counting smart pointers.
The Tinman 3D SDK assumes that these two approaches - regardless of their implementation - have the following properties:
-
Garbage collectors delete whole graphs of unreferenced objects, i.e. cyclic references will not cause memory leaks.
-
Garbage collectors delete unreferenced objects at their own discretion, i.e. at the time they choose, using the thread they choose.
-
Smart pointers do not delete whole whole graphs of unreferenced objects, i.e. cyclic references can cause memory leaks.
-
Smart pointers delete unreferenced objects immediately, i.e. both time and thread are well-defined.
To get the best of both worlds, the Tinman 3D SDK uses the well-known concept of disposable objects (see IDisposable), where the life-time of an object is still managed by the computing environment but a dedicated method exists to explicitly dispose the object (which solves 2.), making it release all resources and references it holds. This effectively breaks cyclic references (which solves 3.).
On top of this, the Tinman 3D SDK uses the concept of ownership: when a disposable object is created, the creating expression receives ownership of it. From there, ownership must be transferred or shared - following the rules defined by IDisposable - until all owners terminate their ownership. Then the object will have been disposed and the computing environment may perform object deletion at its own discretion.
The rules of ownership transfer may be verified using static code analysis. This verification is part of every build of the Tinman 3D SDK. |
Exception Handling
The Tinman 3D SDK uses exception classes for handling exceptional error situations.
For C# and C++, the standard mechanism via throw
/ catch
is used.
All thrown exception objects are instances of TinmanException or one of its subclasses. Each exception object carries the following information:
-
Error Source Tag
This is a short textual token that identifies a member of a type (for example:'TinmanException.Source'
), to ensure that at least a minimalistic stacktrace is available. The native stacktrace provided by the computing environment may be incomplete, inaccurate or simply not existent, for example due to aggressive optimization by the compiler. -
Error Message
This is the error message that has been constructed at thethrow
site. It should be human-readable. Together with the Error Source Tag, this will usually narrow down the set of possiblethrow
sites and error causes to one or two. If possible, please make sure to include this information in your bug reports. -
Error Details
This is a TinmanError object that contains detailed information about the exception, including the Error Source Tag and the Error Message. It may contain zero or more aggregated TinmanError objects, which represent the causes that have led to the error. Additionally, a native stacktrace in an unspecified format may be included, to aid debugging.
In addition to a thrown exception, an error dump file may be generated, in order to provide detailed information on how to reproduce the problem. For details, please refer to TinmanErrorDump. Including error dump files in your bug reports is of great help.
The intention behind the exception handling rules of the Tinman 3D SDK is to avoid use of exceptions where possible in low-level code (to allow compilers to apply aggressive optimization techniques), but to use exceptions where applicable in high-level code (to keep the API as clean as possible). |
There are three kinds of exceptional error situations, each having different handling rules.
Failed Assertions
Assertions are used to validate the internal state of objects and to check the arguments of a called method against the defined preconditions:
These exceptions are never documented as being thrown by a method. Instead, method preconditions are documented for each parameter and state requirements are documented in the description text or can be inferred from the semantic rules.
Assertions are only triggered in DEBUG mode.
In RELEASE mode, failed assertions may lead to unpredictable behaviour.
|
A failed assertion is interpreted as a programmer’s mistake. As a developer who is using the Tinman 3D SDK, please check the error message and consult the documentation to find out what went wrong.
Please keep in mind that it is also possible that a programmer at Tinman 3D made a mistake. If you feel this could be the case, please file a bug report. |
Runtime Errors
A runtime error is a recoverable situation that is not expected under normal operation, but could very well happen in practice (for example, a sharing violation when trying to write to a file).
All TinmanException classes that do not represent failed assertions describe a runtime error. An application may inspect the concrete exception type and analyze the contained error information, in order to perform error recovery at its own discretion.
The exceptions that may be thrown for runtime errors are always listed in the documentation (for example IOException for IFile.Read), including a short description of the error situation.
Critical errors
A critical error is an unrecoverable situation, which should never happen.
Allowing code to throw an exception in these cases would reduce the amount of non-throwing code significantly (which would limit the use of the C++ noexcept
operator, for example).
The Tinman 3D SDK uses dedicated panic methods to handle critical errors:
An application may define its own panic handler with TinmanException.PanicHandler.
If the panic handler returns, the behaviour of subsequently executed code may be undefined. |
Semantic Rules
Attributes and interface are used to establish a number of standard semantic rules, which are used throughout the Tinman 3D SDK.
-
BetaAttribute
The type or member is in beta state and should be considered experimental. -
ConstantAttribute
A property will return the same value throughout the life-time of the enclosing object. -
DebugHelperAttribute
A member provides ancillary features that are solely intended to be used for debugging and testing. -
EmptyBodyAttribute
A virtual member has a trivial body and does not need to be called from overriding members. -
FlagsAttribute
The value of an enumeration is a bit-field and may thus be composed of zero or more enumeration items. -
PureAttribute
The method does not have any side-effects. -
SemanticEnumAttribute
The ordinal values of the enumeration items are used in binary storage formats and must not change. -
ShutdownAttribute
There is only a single global shutdown method for the Tinman 3D SDK: TinmanModule.Shutdown. -
StaticTemplateAttribute
The interface is a static template for high-performance code. -
ThreadSafeAttribute
The member may be invoked concurrently by multiple threads.
The following attributes only need to be considered when using the Code-X workflow (that is, using the CxCodeUnit class to parse, validate and generate source code) to implement own libraries and may be ignored otherwise:
Since Code-X is based on a subset of C#/.NET, the following well-known system types are re-used, which get translated to their semantic counter-parts in other programming environments:
By using well-known system types, language specific code constructs may be used when consuming a Code-X library, for example the foreach and using statements of C#.
|
Begin/end blocks are used frequently in the Tinman 3D SDK.
obj.Begin();
try
{
obj.DoSomething();
}
finally
{
obj.End();
}
Classes that exhibit begin/end semantics implement the IBeginEnd interface. Additional semantic may be present, as indicated by IBeginEndWrite and IBeginEndSync.
Methods that require an active begin/end block (such as the DoSomething
method above) are annotated with BeginEndAttribute or BeginWriteEndAttribute.
API Libraries
This section provides an overview of the types that are present in the libraries of the Tinman 3D SDK.
The libraries of the Tinman 3D SDK contain considerable amount of functionality that is also available in other standard libraries, such as .NET Standard, C++ Standard Template Library or Boost. At first glance, this may seem like "re-inventing the wheel", but actually makes sense when taking the following into account. |
The Tinman 3D SDK is maintained in a subset of the C# programming language (in terms of syntax and semantic), which is named Code-X. Using a proprietary source code analysis and transformation engine, the Code-X sources are transformed to other programming languages (e.g. C++, Java, Rust) in a fully automated process.
For details on the syntax and semantic of Code-X, please refer to CxCodeUnit and Code-X Framework. |
In this context, the benefit of having a concise and well-tested codebase in Code-X with uniform behaviour that can be transformed to other languages outweighs the cost of "re-inventing the wheel" for standard algorithms and data structures.
Core
The Core library contains general-purpose code that is not terrain-specific.
Tinman.Core
-
AlgorithmUtil
General-purpose helpers for common algorithms -
ArrayUtil
General-purpose helpers for dealing with arrays -
StringUtil
General-purpose helpers for strings -
SystemUtil
General-purpose helpers for common programming tasks -
TinmanModule
Base class for code module descriptors
Tinman.Core.Codec
-
IBinaryDigest
Binary hash functions -
IBlockCodec
Binary block-based encryption
Tinman.Core.Collections
-
IBag, IMap, IBijectiveMap
The Collections framework -
Graph
Directed graphs
Tinman.Core.Config
-
ConfigScript
The Scripting framework.
Tinman.Core.Cx
-
CxCodeUnit
The program structure information (PSI) model for Code-X
Tinman.Core.Database
-
BlockStorage
Lightweight embedded block-based database engine
Tinman.Core.Document
-
TextDocument
Object model for simple text-based documents.
Tinman.Core.Formatting
-
CharacterEncoding
Common character encodings -
Format, Label, StringBuilder
Formatting based on placeholder patters -
FormattingUtil
Helpers for doing common formatting, for example metric values -
JsonValue
Reading and writing of JSON values
Tinman.Core.IO
-
Path, IFile, IFileSystem
The Filesystem abstraction layer -
ISerializable, ISerializer
Long-term backward-compatible object serialization -
IEndPoint, ISimpleHttp
The Networking abstraction layer -
IDataStream, ITextStream
Binary and text-based data streams
Tinman.Core.Licensing
-
LicenceDomain.Tinman
The licence domain of the Tinman 3D SDK
Tinman.Core.Logging
-
ILogger, LoggingUtil
The Logging framework
Tinman.Core.Math
-
Fraction, Gauss, Range, SelfInformation, Statistics
Mathematical helpers -
Mandelbrot
The Mandelbrot fractal
Tinman.Core.Privileges
-
Privilege
Privilege-based access control for utilization of native APIs and system resources by the SDK
Tinman.Core.System
-
ByteBuffer
Raw access to binary data -
ErrorBarrier
Barrier for catching errors, includingACCESS_VIOLATION
-
LowLevel
Low-level helpers for dealing with the computing environment -
NativeLibrary
The Native Libraries abstraction layer -
ObjectPoolBase, SimpleObjectPool
Object pooling -
Singleton
Singleton pattern, integrated with Initialization / Shutdown -
Terminal
Access to the console of the enclosing process.
Tinman.Core.Threading
-
Thread, Monitor
The CPU Threading abstraction layer -
IOperation
Asynchronous operations with progress reporting
Tinman.Core.Validating
-
IValidatable, IValidateMessage, ValidateResult
Object validation and error reporting
Tinman.Core.Xml
-
XmlDocument
A minimalistic XML parser and document model.
Terrain
The Terrain library contains all the terrain-specific features of the Tinman 3D SDK. Using this library is sufficient to integrate these feature into an application.
Tinman.Terrain.Buffers
-
ISampleBuffer
Memory buffers for sample grids
Tinman.Terrain.Codec
-
IBufferCodec
Codecs for lossless compression of sample grids -
LodPartition
Level-of-detail partitioning of sample grids
Tinman.Terrain.Datasets
-
DatasetCollection
Shared access to dataset files -
DatasetFileCache
Local caching for dataset streaming -
IDataset
Object model for terrain datasets
Tinman.Terrain.Geometries
-
IGeometry
Base geometry definitions for terrain meshes
Tinman.Terrain.Georef
-
Raster, IGeoRegistry
The Geo References domain model
Tinman.Terrain.Heightmaps
-
IHeightmap
The Terrain Raster Data domain model
Tinman.Terrain.Imaging
-
IImage
The Pixel Images domain model
Tinman.Terrain.Mapping
-
IMapEntity, IMapProjectionFactory
Object model for transformation of rectangular maps and cubemaps
Tinman.Terrain.Procedural
-
INoiseFunction
The Terrain Procedural Data domain model
Tinman.Terrain.Pyramids
-
IPyramid
The Terrain Pyramid Data domain model
Tinman.Terrain.Rendering
-
IGpuBuffer, ITexture, IPrimitiveRenderer, …
The GPU Rendering abstraction layer
Tinman.Terrain.Shapes
-
IShapeReader
The 2D Shapes domain model
Tinman.Terrain.Tools
-
ICanvasFactory
Pixel-perfect rendering of raster or pyramid data on a 2D canvas -
GridLineBuilder
Generation of graticule lines on projected maps -
MapView
Smooth rendering of raster or pyramid data on a 2D canvas with scrolling, zooming and projection
Tinman.Terrain.Util
-
AffineTransform, Transform
Decomposable affine transformations -
AverageValue, Fade, LineStipple, SimpleMap31, SimpleMap63
Common helpers and utilities -
BoxSoup, Points, SpatialTree
Spatial data structures -
Conversions, Interpolation
Common numeric functions -
ICurve
Interpolation of curves -
IColorRamp, ISrgb
Interpolation of sRGB-aware color values -
IFileData
Object model for specifying binary data with file semantics -
IFileFormat
Object model for defining file formats -
MaterialToken, MaterialKeys
The material terrain data layer
Tinman.Terrain.Vertices
-
IVertexArray, IVertexFormat
CPU / GPU vertex data for terrain meshes
Tinman.Terrain.Visibility
-
IVisibleCheck
Functions for CLOD mesh refinement.
Engine
The Engine library provides a lightweight engine for realtime applications. It may optionally be used, in addition to the Terrain library.
Tinman.Engine.API
-
GLBase
Bindings for GL-style C-APIs, such OpenGL and WGL
Tinman.Engine.Application
-
IApplication
The Application framework -
IApplicationWindow
The Window System abstraction layer
Tinman.Engine.Drawing
-
Graphics
The 2D / 3D Graphics framework
Tinman.Engine.GUI
-
Component
The User Interface framework
Tinman.Engine.Models
-
ModelRenderer
The 2D / 3D Graphics framework -
IShapeExtruder, ISpatialQuery, IModelDecorator
Common utilities for 3D models.
Tinman.Engine.Particles
-
ParticleBuffer
Low-level framework for GPU-based particle effects
Tinman.Engine.Profiling
-
IProfiler
Object model for collecting performance profiling values at runtime
Tinman.Engine.Rendering
-
IGraphicsContext
The GPU Rendering abstraction layer -
Renderer
The 2D / 3D Graphics framework
Tinman.Engine.Widgets
-
IWidget
The User Interface framework
AddOns
The AddOns library contains several modules that can be integrated into an application, as required. These modules can be seen as plug-ins, mainly to interface with other 3rd-party libraries.
Tinman.AddOns.Application
-
Example implementations of IApplication and IOperation
-
ConfigValueBrowser implementation for the AddOns library and
Tinman.AddOns.Assimp
-
AssimpModelFormat
The ImageFormat plug-in for the Open Asset Import Library
Tinman.AddOns.Components
-
AnimationGui
The Animation component -
BookmarksGui
The Bookmarks component -
CameraPathGui
The Camera Path component -
DefaultVisibleCheckGui
The Default Visible Check component -
GraphicsContextChooserGui
The Grapics Context Chooser component -
HeightmapSampleGui
The Heightmap Sample component -
LightingGui
The Lighting component -
MapCoordinatesGui
The Map Coordinates component -
MeshBufferGui
The Mesh Buffer component -
MeshDynamicGui
The Mesh Dynamic component -
ProfilerGui
The Profiler component -
ShadowMappingGui
The Shadow Mapping component -
TerrainBufferGui
The Terrain Buffer component -
TerrainLayerListGui
The Terrain Layer List component -
TerrainViewGui
The Terrain View component
Tinman.AddOns.DirectX…
-
DirectX9ContextFactory
The IGraphicsContextFactory plug-in for Direct3D 9 -
DirectX11ContextFactory
The IGraphicsContextFactory plug-in for Direct3D 11 -
DirectX12ContextFactory
The IGraphicsContextFactory plug-in for Direct3D 12
Tinman.AddOns.Editors
-
Example GUI components, see User Interface.
Tinman.AddOns.GDAL
-
GdalImageFormat
The ImageFormat plug-in for GDAL -
ProjCoordinateTransform
The ICoordinateTransform plug-in for PROJ
Tinman.AddOns.Heightmaps
-
Example implementations of the IHeightmap interface.
Tinman.AddOns.MFC
-
Tinman.AddOns.MFC.CApplicationControlMfc
The IApplicationControl plug-in for MFC
Tinman.AddOns.OpenFlight
-
OpenFlightModelFormat, OpenFlightSceneDataFormat
Data importer for OpenFlight models and scenes.
Tinman.AddOns.OpenGL…
-
OpenGLContextFactory
The IGraphicsContextFactory plug-in for OpenGL -
OpenGLESContextFactory
The IGraphicsContextFactory plug-in for OpenGLES
Tinman.AddOns.Pyramids
-
BingMapsPyramid
The IPixelPyramid plug-in for BingMaps -
GoogleMapsPyramid
The IPixelPyramid plug-in for GoogleMaps -
OpenStreetMapPyramid
The IPixelPyramid plug-in for OpenStreetMaps
Tinman.AddOns.Scenes
-
Example implementations and components for the Scene API.
Tinman.AddOns.SQLite
-
MBTilesPyramid
The IPixelPyramid plug-in for MBTiles.
Tinman.AddOns.Widgets
-
CanvasWidget
The Canvas 2D widget -
MapViewWidget
The Map 2D widget -
ModelWidget
The Model 3D widget -
SceneViewWidget
The Scene 3D widget -
TerrainViewWidget
The Terrain 3D widget -
TestWidget
The Test widget -
TextureWidget
The Texture widget
Tinman.AddOns.WinForms
-
Tinman.AddOns.WPF.ApplicationControlWpf
The IApplicationControl plug-in for WPF
Tinman.AddOns.WPF
-
Tinman.AddOns.WinForms.ApplicationControl
The IApplicationControl plug-in for WinForms
Processor
The Processor library contains the source code of the Geodata Processor tool. It is contained in Source licence.
Tinman.Processor
-
AsciiWriter
Dumps pixel data to the console as ASCII-art. -
Cli
The main class / command-line interface of the processor tool. -
ConfigDocFormatter
Formats help content for output on the console.
Demo
The Demo library contains all examples and tutorials of the Demo Application.
Tinman.Demo.Examples
-
Example_Application, …
The source code examples
Tinman.Demo.Tutorials
-
Tutorial
The tutorials of the Demo Application
Abstraction Layers
This section gives an overview of the abstraction layers in the Tinman 3D SDK, which provide platform-agnostic API entry points to platform-specific features.
In Code-X, platform-specific code is wrapped in so-called native regions, which must be implemented for each programming language and for each platform. During the source code transformation process, the contents of native regions is retained, by tracing it with the native region GUID.
With a source code licence, it is possible to modify or re-implement all native regions of the Tinman 3D SDK. |
// NATIVE: Flush file buffers.
// THROWS: IOException
#region Native {49EF28C7-AC50-465E-85EC-AB0BAB73CF56} (1)
if (LowLevel.IsPosix) (2)
{
if (SafeNativeMethods.fsync((int) hFile) != 0)
throw IOException.FileError("LocalFile.Flush", pathInfo);
}
else if (LowLevel.IsWindows)
{
if (!SafeNativeMethods.FlushFileBuffers(hFile))
throw IOException.FileError("LocalFile.Flush", pathInfo);
}
else (3)
{
try
{
lock (stream)
stream.Flush(true);
}
catch (Exception e)
{
throw IOException.FileError("LocalFile.Flush", pathInfo, e);
}
}
#endregion
1 | Native region marker |
2 | Platform detection |
3 | Fallback to .NET Standard |
// NATIVE: Flush file buffers.
// THROWS: IOException
NATIVE_REGION_BEGIN ("49EF28C7-AC50-465E-85EC-AB0BAB73CF56") (1)
#if defined(CODEX_PLATFORM_POSIX) (2)
if (fsync(hFile) != 0)
throw IOException::FileError("LocalFile.Flush"_CX, pathInfo);
#elif defined(CODEX_PLATFORM_WIN)
if (!FlushFileBuffers(hFile))
throw IOException::FileError("LocalFile.Flush"_CX, pathInfo);
#else
#error CODEX_PLATFORM_UNSUPPORTED
#endif
NATIVE_REGION_END ("49EF28C7-AC50-465E-85EC-AB0BAB73CF56")
1 | Native region marker |
2 | Platform detection |
CPU Threading
Using CPU threads is simple: first, implement a IThreadMain class. This is the thread’s main function. Then, create a Thread object, which will wrap the thread main function and run it in the background.
To synchronize between threads, create and use Monitor objects, as required.
The C# SDK contains additional debug helpers, to detect performance issues caused by overuse of thread synchronization:Monitor.DebugDumpMonitorUsage Monitor.DebugDumpWaitTimes
|
The RingBuffer class may be used to control the data flow between a producer and a consumer.
Lengthy background operations are be represented with IOperation objects. The IOperationOps interface may be used to aggregate and combine existing operations in different ways. The RunningOperation class provide a simple way of running a background operation, while tracking its progress.
The ITask and ITaskVoid interfaces represent tasks which can be scheduled by the TaskPool for background execution. The TaskResult and TaskResultVoid classes represent the pending result of a background task. With ITask.Queue, a ITaskQueue object can be created, which provides control over how tasks are executed.
Filesystem
A file system is represented with the IFileSystem interface, which provides access to the contained files, which in turn are represented as IFile objects. Files are referred to with Path values.
Creating a FileSystem object will add it to the global list of active file systems, which is used to find the file system that knows how to open a given Path value, see FileSystem.From. Disposing a FileSystem object will remove it from the global list.
The grammar of Path values is designed so that it captures all common file path flavours:c:\Windows\File.txt \\UNC\$c\Windows\File.txt /unix/file https://website.net/file.txt
|
These are the built-in file system implementations of the Tinman 3D SDK:
-
LocalFileSystem
This is the local filesystem of the computing environment. -
HttpFileSystem
A file system that treats Path values as URLs and GETs the HTTP / HTTPS resource. -
MemoryFileSystem
A file system that holds its files in memory. This is used by TinmanModule.Resource to access embedded library resources. -
VirtualFileSystem
A file system that aggregates selected files of other file systems. The Demo Application is using a virtual file system to access all its assets.
GPU Rendering
The access to GPU rendering features is provided through a number of interfaces. This API is divided between the Terrain and Engine libraries.
Terrain
The IBufferFactory interface is used to create GPU buffers. The ITextureFactory interface is used to create texture resources. Buffers and textures are processed by the Low-level Terrain API:
-
IVertexBuffer
Per-vertex data of CLOD terrain meshes -
IIndexBuffer
The result of CPU/GPU triangulation -
IGpuBuffer
Mesh structure data for GPU triangulation -
ITexture2D
Material-based texturing / Texture-atlas rendering
An application may then use suitable implementations of the following interfaces to perform GPU rendering with its own graphics engine:
-
IPrimitiveRenderer
Rendering of graphics primitives, using vertex and index buffers. -
IWorkDispatcher
Performing computations on the GPU, for example mesh triangulation. -
IMeshDispatcher
Direct rendering of CLOD meshes, using Mesh Shaders.
Engine
The High-level Terrain API is built around the IGraphicsContext interface, which represents the basis of the graphics engine of the Tinman 3D SDK.
To obtain a graphics context, an instance of IGraphicsContextFactory must be created and configured. There is a dedicated implementation class for each supported render API.
GPU programs are represented with IRenderEffect objects. A render effect exposes its parameters in a way that is independent of the underlying render API, by implementing a subclass of RenderEffectParameters.
A render effect must have a separate implementation for each render API. An instance of IRenderEffectFactory is used to choose the render effect implementation, based on a given graphics context and the given render effect parameters.
Native Libraries
The NativeLibrary class is used to interface with native libraries, which involves the following steps:
-
Find a native library binary and load it.
-
Query native function pointers.
-
Wrap native functions with platform-agnostic API entry points.
Step 1. is controlled by implementing LibraryNames and may further be hinted by providing additional library search paths via AddSearchPath.
Step 2. is performed by subclasses of NativeLibrary, which use GetFunctionPointer to discover native functions.
Step 3. is implemented by the source code parts of the NativeLibrary subclasses that are covered by Code-X translation, as described in API Libraries.
Subclasses of NativeLibrary are usually an implementation detail and are not part of the public API, for example AssimpModelFormat or GdalImageFormat. |
Classes that deal with native functionality usually implement the INativeHandle interface, which allows client code to interface with the underlying objects directly.
For details on how to interpret the native handle value, please refer to the documentation of the respective class. |
Networking
The Tinman 3D SDK provides networking capabilities for streaming geodata at runtime. Streaming may be performed over HTTP / HTTPS or plain TCP/IP.
Web Requests
Web resources may be fetched with HTTP GET requests by using the ISimpleHttp interface, usually through an instance of the standard implementation SimpleHttp.
The SimpleHttp class uses the HTTP / HTTPS features of the computing environment. It does not re-implement the network stack for web requests. |
For full control over web requests, client code may choose to use its own implementation of ISimpleHttp. Alternatively, client code may use custom implementations of ISimpleHttpImage and ISimpleHttpText to provide image and text decoding capabilities that are not part of the Tinman 3D SDK.
Client/Server
Synchronous stream-based client-server communication is provided by the IEndPoint interface, which acts as a factory for IServer and IConnection objects.
The standard implementation SocketEndPoint uses the TCP/IP stack of the computing environment. |
Window System
The Engine library allows to create a separate top-level application window via ApplicationWindow.Create, for running IApplication objects directly on the desktop of the computing environment.
For integration with a specific GUI framework, use one of the IApplicationControl implementations, which use a custom implementation of the IApplicationWindow interface that is not exposed to client code.
By writing custom implementations of IApplicationWindow, client code may provide additional GUI framework integrations, which are not part of the Tinman 3D SDK.
Domain Models
Several non terrain-specific domain models are part of the Tinman 3D SDK. These are used to access and import data. An overview of the domain models is presented here. For further details on how to import data, please refer to Geodata Processing.
Pixel Images
A common way to provide geodata is in form of regular sample grids, which are typically stored a as image files (for example: GeoTIFF, PNG, JPEG). A geo-reference may be present, which maps the center of image pixels to map coordinates.
Image data is processed incrementally, one pixel row after another, using IImageReader objects. During processing, various operations and conversions may be applied, as defined by IImageOps.
Image readers are obtained from image descriptors, which are IImage objects. In most cases, an image descriptor is created for a file with Image.File and then modified, for example by Scaling. Image descriptors may also be defined via scripting by using the Image type.
In theory, image files include all the required metadata to import the image data properly. In practice, it is often the case that some metadata is missing and other metadata is wrong. In these cases, image descriptors need to be adjusted manually. These are the most common pitfalls:
-
sRGB / Gamma
If gamma correction is not applied correctly, surface textures will look wrong (too dark, to bright), whereas digital elevation models will have incorrect height values. -
Value range
The range of pixel values (for example 0 to 255 for 8-bit grayscale images) must be mapped to elevation and displacement ranges (for example -10,000m to +10,000m) at some point. It might be necessary to adjust the pixel range of the source image, in order to achieve optimal quality and compression. -
Pixel coverage
The pixel coverage defines how to interpret pixel coordinates with respect to geo-referencing. If the pixel coverage is wrong, inaccuracies will be introduced, having the same magnitude as the ground sample distance. -
No-data value
Geodata often defines a special pixel value as void, to identify image regions for which no data is present. Those regions must be imported and processed in a different way than transparent regions, which are defined by pixel alpha values. -
Data layer
Image data may be interpreted in different ways, for example as elevation models or surface imagery (see HeightmapLayer). If the layer is not chosen correctly, geodata will not show up as expected.
To write an image file in a supported image format, use ImageFormat.WriteImage to obtain an IImageWriter object. The writer will consume the data of a given IImageReader object and write the resulting image file.
For additional information, please refer to Geodata Processing / Pixel Data.
2D Shapes
In the Tinman 3D SDK, shapes are represented with IShape objects, which define a signed distance field in a two-dimensional coordinate system. In addition to the distance function, a shape may provide information about its geometric structure, by exposing its vertices, edges, polygon contours and triangles.
To obtain a IShape object, use one of the constructors of the Shape class. This includes loading a shape from a supported shape file format. Shapes may also be constructed via scripting by using the Shape class.
If a shape file contains more than one shape definition, Shape.File will use a Shape.Group to represent them. To read the shape definitions sequentially, use ShapeFormat.ReadShapes and then consume the returned IShapeReader. |
To build a geometric shape from scratch, use a ShapeBuilder object, which may be obtained from Shape.Geometry.
For additional information, please refer to Geodata Processing / Shape Data.
Rasterization
Shapes can be rasterized in order to generate terrain data dynamically at runtime:
-
HeightmapShape, CorrectionLayer
The shape will modify the IHeightmap of a terrain, for example to flatten the terrain around runways or to define roads. -
PixelPyramidShape
The shape will generate pixel data for the tiles of a pixel pyramid.
Since the generated terrain data itself is represented as IHeightmap and IPixelPyramid objects, it may be combined freely with other terrain data objects.
A common use for vector shapes is to crop terrain data to specific regions of interest (see TextureLayer.Region). |
Geometric Subdivision
When transforming geometric shapes, subdivision is performed in order to maintain a given accuracy. This creates additional vertices, edges and triangles.
Subdivision is required because a conversion between two coordinate systems may not be representable with an affine transformation matrix:
-
IShapeOps.TransformGeometry
An immediate transformation to another coordinate system, where subdivision may either be applied in the target 2D coordinate system or in the corresponding 3D geocentric coordinate systemSubdivision in the 3D geocentric coordinate system is usually required when extruding shapes to 3D models via IShapeExtruder. -
IShapeOps.TransformCoordinateSystem
A deferred transformation to another coordinate system that delegates to 1. -
IShapeOps.TransformCubemap
A deferred transformation to the faces of a geographic cubemap coordinate system that delegates to 1.
3D Models
In the Low-level Terrain API, there is no use for 3D models.
The High-level Terrain API allows to place models on a TerrainMesh, by using TerrainModel objects. Terrain data can be produced by ray-casting 3D models, yielding elevation and displacement data, which may be used as a brush for terrain painting, for example.
The Scene API builds on that and adds features for populating interactive 3D scenes with 3D model data which are not strictly terrain-specific.
A 3D model is represented as a hierarchy of IModel objects, where a parent model may have zero or more child models, associating an affine transformation with each one.
This object model is a subset of glTF 2.0, with only minor semantic and structural differences. |
Actual 3D geometry is represented with IModelPart objects, where a model may have zero or more model parts. A model part contains this information:
-
A reference to a shared model geometry buffer
-
A batch that represents graphics primitives in the referenced buffer, such as points, lines and triangles
-
A reference to a shared material definition, which uses textures for physically-based rendering (PBR)
The interfaces of this object model provide read-only access. For read/write access, use the default implementation class.
Read-only | Read/Write |
---|---|
To modify a 3D model through the read-only interfaces without changing its actual definition, use a ModelPose object and change the transformation or material of selected IModel objects.
To actually modify the definition of a 3D model, use IModel.Mutable, IModelPart.Mutable and IMaterial.Mutable to obtain a read/write object reference.
For spatial queries such as picking or distance computation, use the IModelCollider object that may be obtained via IModel.Collider. Use IModelCollider.ColliderWithPose to obtain a model collider that takes the current state of the given ModelPose into account.
For additional information, please refer to Geodata Processing / Model Data.
Geo References
The Raster class provides a complete definition of a geo-reference for a sample raster. The corresponding scripting type is Tinman.Georef.
The CoordinateSystem class provides a complete definition of a geo-reference for a rectangular map. The corresponding scripting type is CoordSys.
The object model for geo-references is based on the IGeoObject interface. Prebuilt object model graphs are provided via GeoRegistry objects.
The Tinman 3D SDK contains a registry for the EPSG Geodetic Parameter Dataset. After loading the registry file epsg-*.dat, it can be accessed from scripting with EPSG.
Frameworks
The following sections describe the frameworks that are contained in the Tinman 3D SDK.
2D / 3D Graphics
Based on the GPU Rendering abstraction layer, the following render utilities exist in the Engine library.
-
Graphics
Rendering of 2D primitives (circles, lines, boxes, polygons), shapes, bitmaps and pixel fonts -
IScreenLabel / ScreenLabelPlacement
Placement of screen labels, taking into account mutual occlusion, with associated depth for correct rendering in a 3D scene -
Renderer
Rendering of 3D primitives (points, lines, triangles), boxes and soups -
ModelRenderer
Rendering of hierarchical 3D models with state sorting and batching, instancing and view-frustum culling. -
ShadowMapping
Shadow mapping with cascaded light-space perspective shadow-maps -
Sky / IEnvironmentMap / SunPosition
Sky rendering
Application
The Application framework of the Engine library provides the IApplication interface, which can be implemented in order to create a realtime application. Based on the 2D / 3D Graphics and Window System abstraction layers, it provides standard means to run an application in different environments.
The Hello World example shows how to create and run an application with minimal code. |
To run an application, use the IApplication.Run method. To host an application in a native GUI framework, use one of the IApplicationControl implementations.
Extend the WidgetApplication class if you want your application to run one or more IWidget objects. |
The application cycle is defined and performed by the ApplicationLoop class. If required, the application loop may be invoked by client code (see ApplicationLoop.Main), but usually this is done automatically by a helper in the SDK.
An application may optionally interact with its execution context through the IApplicationRunner interface, for example to toggle full-screen mode.
Collections
A simple collection framework is included in the Core library.
Where applicable, integration with the native APIs is provided (for example, to be able to use foreach
alike statements).
Collection classes always implement the IEnumerable interface, where the contained elements are returned by an IEnumerator object.
Collection classes may implement the ICollector interface, which provides a standard way to populate a collection with elements.
Definitions for element equality, sort order and hash values are provided by EqualsDelegate, CompareDelegate and HashCodeDelegate. If a definition is omitted, the default definition is used (see EqualsDefault and HashCodeDefault).
By convention, a collection object should be accessed through one of the collection interfaces.
Separate interfaces exist for immutable access (having a Const name suffix) and mutable access (no name suffix).
|
Semantic | Immutable | Mutable | Default Implementation |
---|---|---|---|
bag |
|||
sorted set |
|||
list |
|||
deque |
- |
||
map |
|||
sorted map |
|||
bijective map |
Logging
The Logging framework in the Core library is used to output log messages at runtime.
The purpose of this framework is not to re-implement yet another logging framework, but rather to provide a common API for logging purposes, especially for Code-X compliant code. |
Log messages are generated via ILogger objects, which must be obtained with LoggingUtil.GetLogger. Each logger object belongs to a ILoggerCategory, which may also have a parent category.
Generated log messages are processed by zero or more ILogMessageHandler objects, for example by outputting a message on the process console or writing an entry into a logfile. Log message handlers may be attached to loggers and categories.
Each log message has a verbosity level, which is compared against the verbosity level of the log message source and the log message handlers, to decide whether to process the log message or not.
To quickly setup a standard configuration for logging, use LoggingUtil.BasicConfiguration. |
Parsing
The Parsing framework in the Core library can be used to specify context-free grammars, using a proprietary specification language similar to the Extended Backus-Naur form (EBNF).
After constructing a Grammar object, it can be used to parse textual input, perform syntax analysis and output the resulting abstract syntax tree (AST).
Passing RuleToSourceFlags.GrammarAst to Grammar.ToSource produces the tree grammar, which is helpful for implementing code that consumes an AST. |
The Grammar class does not use a separate lexer step. Instead, the stream of input characters is consumed directly by the grammar rules.
Scene
The Scene framework in the Engine Engine library implements the Scene API, which can be used to create interactive 3D scenes, based on a realtime 3D terrain.
This framework provides ready-to-use solutions for the features of the Tinman 3D SDK. It is the highest level API and can be used by developers to create 3D applications.
With a source code licence, it can serve as reference and example on how to add terrain features to an existing 3D application engine.
For details on the Scene framework, please refer to Scene Overview.
Scripting
The Scripting framework in the Core library defines a simple script language for describing the construction of native API objects, via IConfigurator objects.
By implementing the IConfigurable interface, native API objects become convertible to script code, which can be used to re-construct the object in its current state.
For details on the Scripting framework, please refer to Scripting Overview.
The Workshop Application can be used to edit scripts, construct native API objects and inspect them.
User Interface
The User Interface framework in the Engine library can be used to add a graphical user interface (GUI) to an IApplication or IWidget.
Its main purpose is to allow a developer to setup GUI controls quickly from code, to do rapid prototyping, testing and development.
For details on the User Interface framework, please refer to User Interface.