Simulation

Stepping the Simulation

In order to step the simulation, parameters are needed. The NvFlowGridParams system is used to deliver a snapshot of all simulation parameters. The map() functionality is necessary to make sure the operation is thread safe. With a mapped parameter snapshot, simulate() can be called to produce NvFlowContext commands and update Grid internal state.

extern NvFlowLoader loader;
extern NvFlowContextInterface contextInterface;
extern NvFlowDeviceManager* deviceManager;
extern NvFlowDevice* device;
extern NvFlowDeviceQueue* deviceQueue;
extern NvFlowGrid* grid;
extern NvFlowGridParamsNamed* gridParamsNamed;

NvFlowContext* context = loader.deviceInterface.getContext(deviceQueue);

NvFlowGridParams* gridParams = loader.gridParamsInterface.mapGridParamsNamed(gridParamsNamed);

NvFlowGridParamsDesc gridParamsDesc = {};
NvFlowGridParamsSnapshot* paramsSnapshot = loader.gridParamsInterface.getParamsSnapshot(gridParams, absoluteSimTime, 0llu);
if (loader.gridParamsInterface.mapParamsDesc(gridParams, paramsSnapshot, &gridParamsDesc))
{
	loader.gridInterface.simulate(
		context,
		grid,
		&gridParamsDesc,
		NV_FLOW_FALSE
	);

	loader.gridParamsInterface.unmapParamsDesc(gridParams, paramsSnapshot);
}

Flushing GPU Work

If using the default Vulkan NvFlowContext implementation, accumulated NvFlowContext commands must be flushed to the GPU. Flushing provided a frameID that can be used for synchronization. getLastFrameCompleted() is useful to run fully async, since it discloses the latency of active GPU work.

NvFlowUint64 flushedFrameID = 0llu;
int deviceReset = loader.deviceInterface.flush(deviceQueue, &flushedFrameID, waitSemaphore, signalSemaphore);
if (deviceReset)
{
	printf("Flow device reset!\n");
	invalid = true;
}

if (cpuWait)
{
	loader.deviceInterface.waitForFrame(deviceQueue, flushedFrameID);
}

NvFlowUint64 lastCompletedFrame = contextInterface.getLastFrameCompleted(context);

Manual Parameter Snapshot

For testing, it is possible to describe a snapshot of simulation parameters explicitly. A database snapshot is composed of snaphots for each datatype. The datatype snaphots for each type are formed out of an array of pointers to the C structs containing the parameters. The database snapshot is then included in the grid params snapshot, which adding timing information, global reset, and a user defined payload. The grid params snapshot can then be committed to grid params, for later access by simulate.

Note, by design commitParams does not perform a deep copy of the data. Parameter values and memory should not change until minActiveVersion from gridParams getVersion() indicates they are no longer needed. In the below example static is used to meet this requirement.

static NvFlowGridSimulateLayerParams testSimulate = NvFlowGridSimulateLayerParams_default;
static NvFlowGridEmitterSphereParams testSpheres = NvFlowEmitterSphereParams_default;
static NvFlowGridOffscreenLayerParams testOffscreen = NvFlowGridOffscreenLayerParams_default;
static NvFlowGridRenderLayerParams testRender = NvFlowGridRenderLayerParams_default;

testSimulate.nanoVdbExport.enabled = NV_FLOW_TRUE;
testSimulate.nanoVdbExport.readbackEnabled = NV_FLOW_TRUE;

static NvFlowGridSimulateLayerParams* pTestSimulate = &testSimulate;
static NvFlowGridEmitterSphereParams* pTestSpheres = &testSpheres;
static NvFlowGridOffscreenLayerParams* pTestOffscreen = &testOffscreen;
static NvFlowGridRenderLayerParams* pTestRender = &testRender;

static NvFlowUint64 version = 1u;

static NvFlowDatabaseTypeSnapshot typeSnapshots[4u] = {
	{version, &NvFlowGridSimulateLayerParams_NvFlowReflectDataType,  (NvFlowUint8**)&pTestSimulate,  1u},
	{version, &NvFlowGridEmitterSphereParams_NvFlowReflectDataType,  (NvFlowUint8**)&pTestSpheres,   1u},
	{version, &NvFlowGridOffscreenLayerParams_NvFlowReflectDataType, (NvFlowUint8**)&pTestOffscreen, 1u},
	{version, &NvFlowGridRenderLayerParams_NvFlowReflectDataType,    (NvFlowUint8**)&pTestRender,    1u}
};
static NvFlowDatabaseSnapshot snapshot = {
	version,
	typeSnapshots,
	4u
};

static NvFlowGridParamsDescSnapshot gridParamsDescSnapshot = { snapshot, 0.0, 1.f / 60.f, NV_FLOW_FALSE, nullptr, 0u };

loader.gridParamsInterface.commitParams(paramSrc, &gridParamsDescSnapshot);

Flow Database

NvFlowDatabase helps version parameters and automate the creation of snapshots. NvFlowDatabaseInterface allows for callbacks during traversal of Flow parameters. This allows efficient read/write access to all the parameters in the database.

For minimal functionality, it is possible to start with a null implementation of NvFlowDatabaseInterface. GridParams provides a list of supported types, the below code initializes the database to support these types.

static const NvFlowDatabaseInterface iface = {};

static NvFlowArray<const char*> typenames;
static NvFlowArray<const char*> displayTypenames;
static NvFlowArray<const NvFlowReflectDataType*> dataTypes;
static NvFlowArray<NvFlowDatabaseType*> types;

// get type count
NvFlowUint64 typeCount = 0u;
loader.gridParamsInterface.enumerateParamTypes(gridParams, nullptr, nullptr, nullptr, &typeCount);

// query Flow types
typenames.reserve(typeCount);
typenames.size = typeCount;
displayTypenames.reserve(typeCount);
displayTypenames.size = typeCount;
dataTypes.reserve(typeCount);
dataTypes.size = typeCount;
loader.gridParamsInterface.enumerateParamTypes(gridParams, typenames.data, displayTypenames.data, dataTypes.data, &typeCount);

// register types
types.size = 0u;
for (NvFlowUint64 typeIdx = 0u; typeIdx < dataTypes.size; typeIdx++)
{
    types.pushBack(gridParamsSet.createType(dataTypes[typeIdx], displayTypenames[typeIdx]));
}

// as a test, create one of each type
NvFlowUint64 stagingVersion = 0llu;
NvFlowUint64 minActiveVersion = 0llu;
loader.gridParamsInterface.getVersion(gridParams, &stagingVersion, &minActiveVersion);
for (NvFlowUint64 typeIdx = 0u; typeIdx < types.size; typeIdx++)
{
	gridParamsSet.createInstance<&iface>(nullptr, stagingVersion, types[typeIdx], "test", "test");
}

Per frame updates

// get version information for this frame's update
NvFlowUint64 stagingVersion = 0llu;
NvFlowUint64 minActiveVersion = 0llu;
loader.gridParamsInterface.getVersion(gridParams, &stagingVersion, &minActiveVersion);

// database update, this will call NvFlowDatabaseInterface if not null implementation
gridParamsSet.update<&iface>(nullptr, stagingVersion, minActiveVersion);

// produce grid params snapshot from database
NvFlowGridParamsDescSnapshot snapshot = {};
gridParamsSet.getSnapshot(&snapshot.snapshot, stagingVersion);
snapshot.absoluteSimTime = absoluteSimTime;
snapshot.deltaTime = simDeltaTime;
snapshot.globalForceClear = globalForceClear;

// submit snapshot to grid params for use on destination thread
loader.gridParamsInterface.commitParams(gridParams, &snapshot);

Cleanup

// mark all instances for delete, but do not free memory
gridParamsSet.markAllInstancesForDestroy<&iface>(nullptr);
// frees memory
gridParamsSet.destroy<&iface>(nullptr);