Low-Level Rendering (RHI)
The Render Hardware Interface (RHI) is the engine’s thin abstraction over Vulkan: memory, textures, pipelines, command lists, and submission. The whole RHI is exposed to C# as a direct, 1:1 binding, so a script can allocate GPU memory, build a pipeline, record commands, and dispatch work on the GPU.
This is the lowest-level surface in the scripting API. Every call is a thin pointer into the matching native function, handles and descriptors cross the boundary by value with no marshalling, and the command recorders skip the GC transition, so the C# RHI runs at the speed of the C++ RHI.
What you drive, and what the engine owns
Section titled “What you drive, and what the engine owns”The RHI surface in C# is deliberately scoped to the operations a script may safely perform. The device, the per-frame loop, and the window swapchain are owned by the engine and are not on this surface. There is no device create or destroy, no frame tick, no present, and no device-wide stall. A script creates resources, records and submits work, and waits on its own submissions with a timeline semaphore.
| You drive | The engine owns |
|---|---|
| GPU memory, textures, pipelines, semaphores, the bindless heap | The GPU device (create, destroy) |
| Command list recording and submission | The per-frame loop and frame pacing |
| Barriers and per-submission synchronization | The window swapchain and present |
| Device and memory introspection | Vsync and the backbuffer |
The two classes
Section titled “The two classes”Everything lives in the LuminaSharp.Rendering namespace, across two static
classes.
RHIis the core surface: memory, resources, the texture heap, command lists, submission, theCmd*recorders, and introspection. Call sites readRHI.Malloc(...),RHI.CreateTexture(...),RHI.CmdDispatch(...), mirroring the C++RHI::functions.RHICoreis the runtime support layer: the global texture and sampler heap, the per-frame transient ring, deferred frees, and pipeline creation from the engine shader library by name.
using LuminaSharp.Rendering;Resource handles
Section titled “Resource handles”Resources are referenced by opaque handles, not objects. Each handle
(FTextureH, FPipelineH, FTextureHeapH, FSemaphoreH, FDepthStencilH,
FCmdListH) is a single 8-byte value you copy freely. A handle is just a
number, so pass it around as you like.
FTextureH Texture = RHI.CreateTexture(Desc);if (Texture.IsValid) // zero is the invalid handle{ // ... RHI.FreeH(Texture); // you free what you create}FreeH is overloaded for every handle type. You own the lifetime of anything
you create, so free it when you are done, the same as delete in C++.
GPU memory
Section titled “GPU memory”GPUPtr is a device-addressable GPU memory pointer (a 64-bit device address).
Allocate it with RHI.Malloc, map it to a CPU address with RHI.ToHost, and
release it with RHI.Free. GPUPtr supports byte-offset arithmetic, and zero
is the null pointer.
GPUPtr Buffer = RHI.Malloc(1024, EMemoryType.CPUWrite);
unsafe{ float* Cpu = (float*)RHI.ToHost(Buffer); // CPU-visible address Cpu[0] = 1.0f;}
RHI.Free(Buffer);The memory type decides where the allocation lives. Both CPU-visible types are mapped and fully readable and writable from the CPU: the name is a hint about the dominant access pattern, not a restriction.
EMemoryType | CPU access | Tuned for | ToHost mappable |
|---|---|---|---|
CPUWrite (default) | Read and write | CPU writes and fast GPU reads (uploads, args). Lives in device-local ReBAR memory, so CPU reads work but are slow (uncached). | Yes |
CPURead | Read and write | CPU readback. Lives in cached host memory, so CPU reads are fast; the GPU reaches it across PCIe. | Yes |
GPUOnly | None | GPU-only working data. Fastest for the GPU, device-local, not mapped. | No |
Malloc(Size) defaults to 16-byte aligned CPUWrite, which is read and
write: you can both fill it from the CPU and read it back, so the default is a
fine choice when you do not want to think about the distinction. The memory type
and alignment are optional arguments, so pass Malloc(Size, EMemoryType.CPURead)
when the CPU reads a lot of GPU-produced data and you want those reads cached, or
give an explicit alignment with Malloc(Size, alignment, type). Calling ToHost
on GPUOnly memory is invalid: to fill device-local memory, upload through a
CPU-visible staging allocation and a copy command.
How a shader reads its arguments
Section titled “How a shader reads its arguments”The RHI uses a single, uniform argument model for every draw and dispatch. Each
command takes one GPUPtr (named DrawArgs) that points at a small struct in
GPU memory. The engine binds that pointer as the shader’s only push constant,
and the shader reads it back with GetArgs<T>().
In a shader (.slang), include the RHI conventions and declare the args struct:
#include "Includes/GlobalRHI.slang"
struct FMyArgs{ float* Input; // a GPU device address (a buffer) float* Output; uint Count;};
// GetArgs<T>() returns the struct the dispatch pointed at.FMyArgs* Args = GetArgs<FMyArgs>();On the C# side you build a byte-identical struct, fill it (including the device
addresses of any buffers it references), and pass its GPUPtr to the dispatch
or draw call. The fields line up by offset, so a shader float* matches a C#
GPUPtr, and a shader uint matches a C# uint.
This is the same args model the engine’s own passes use, so anything you can express in an engine shader works identically from a script.
The bindless texture heap
Section titled “The bindless texture heap”Textures and samplers are not bound one at a time. They live in a single global
bindless heap, and a shader indexes them by an integer slot. RHICore
exposes the engine’s global heap:
FTextureHeapH Heap = RHICore.GetGlobalHeap();
uint TexSlot = RHI.HeapWriteTexture(Heap, Texture); // sampled-image slotuint UavSlot = RHI.HeapWriteRWTexture(Heap, Texture, 0); // storage-image slot (mip 0)uint SmpSlot = RHI.HeapWriteSampler(Heap, FSamplerDesc.LinearWrap);Each HeapWrite* returns the slot index. You pass that integer to your shader
(through the args struct), and the shader reads the resource from the matching
global array:
float4 Color = SampleTexture2D(Args.TexSlot, SAMPLER_LINEAR_WRAP, UV);gRWTextures2D[Args.UavSlot][Texel] = Color;Release a slot with HeapFreeTexture, HeapFreeRWTexture, or
HeapFreeSampler when the resource goes away. The global heap is pre-populated
with a set of stock samplers, so most shaders never create their own:
| Stock sampler slot | Constant in GlobalRHI.slang |
|---|---|
| 0 | SAMPLER_LINEAR_WRAP |
| 1 | SAMPLER_LINEAR_CLAMP |
| 2 | SAMPLER_LINEAR_MIRROR |
| 3 | SAMPLER_POINT_WRAP |
| 4 | SAMPLER_POINT_CLAMP |
| 5 | SAMPLER_ANISO_WRAP |
| 6 | SAMPLER_ANISO_CLAMP |
For a compute pass that uses the heap, bind it once on the command list with
RHI.CmdSetTextureHeap(CL, Heap) before dispatching.
Command lists, queues, and submission
Section titled “Command lists, queues, and submission”GPU work is recorded into a command list and then submitted to a queue. Open a
list with RHI.OpenCommandList, record Cmd* calls into it, and submit it.
FCmdListH CL = RHI.OpenCommandList(EQueueType.Compute);RHI.CmdSetPipeline(CL, Pipeline);RHI.CmdDispatch(CL, ArgsPtr, GroupsX, 1, 1);RHI.Submit(CL, EQueueType.Compute);There are three queues, selected by EQueueType: Graphics (the default),
Compute, and Transfer. Command-list recording is per-thread and
lock-free, so you can build lists on worker threads; creation and submission
are the synchronized operations.
To wait for work to finish, signal a timeline semaphore on submit and wait
on it. A timeline semaphore is a monotonically increasing 64-bit counter:
RHI.CreateSemaphore(start) makes one, the submit signals it to a value, and
RHI.WaitSemaphore(sem, value) blocks the calling thread until the GPU reaches
that value.
FSemaphoreH Done = RHI.CreateSemaphore(0);
FSemaphoreInfo[] Signal = { new FSemaphoreInfo(Done, 1, EStageFlags.Compute) };RHI.Submit(EQueueType.Compute, new[] { CL }, default, Signal);
RHI.WaitSemaphore(Done, 1); // block until the GPU signals 1RHI.FreeH(Done);The same overload takes a span of waits, so one submission can wait on another’s semaphore to chain passes across queues.
Barriers
Section titled “Barriers”The GPU runs work out of order unless you tell it not to. A barrier orders
one set of pipeline stages before another so that writes are visible to later
reads. Record one with RHI.CmdBarrier(CL, before, after) using EStageFlags,
or use the named helpers in RHI.Barriers for the common cases.
| Helper | Orders |
|---|---|
RHI.Barriers.ComputeToAll(CL) | Compute writes before any later stage reads them. |
RHI.Barriers.RasterToRead(CL) | Color or depth output before a later sample. |
RHI.Barriers.RasterToRaster(CL) | One raster pass before the next. |
RHI.Barriers.AllToTransfer(CL) / TransferToAll(CL) | Around a copy or blit. |
To read GPU results back on the CPU, barrier from the producing stage to
EStageFlags.Host:
RHI.CmdBarrier(CL, EStageFlags.Compute, EStageFlags.Host);Worked example: dispatch a compute shader and read back the results
Section titled “Worked example: dispatch a compute shader and read back the results”This runs a compute shader that doubles every number in a buffer, then reads the results back to the CPU. It is the smallest end-to-end RHI program: a pipeline, two buffers, an args struct, a dispatch, and a semaphore wait.
The shader
Section titled “The shader”Place a DoubleNumbers.slang in the engine shaders directory so the shader
library can compile it by name. The args struct holds the two buffer addresses
and the element count.
#include "Includes/GlobalRHI.slang"
struct FDoubleArgs{ float* Input; float* Output; uint Count;};
[shader("compute")][numthreads(64, 1, 1)]void ComputeMain(uint3 Tid : SV_DispatchThreadID){ FDoubleArgs* Args = GetArgs<FDoubleArgs>();
uint Index = Tid.x; if (Index >= Args.Count) return;
Args.Output[Index] = Args.Input[Index] * 2.0;}The script
Section titled “The script”The C# DoubleArgs struct mirrors the shader struct field for field. A shader
float* is a device address, so it maps to a C# GPUPtr.
using System.Runtime.InteropServices;using LuminaSharp.Rendering;
[StructLayout(LayoutKind.Sequential)]struct DoubleArgs{ public GPUPtr Input; public GPUPtr Output; public uint Count;}
public static unsafe float[] DoubleOnGPU(float[] Values){ int Count = Values.Length; ulong Bytes = (ulong)(Count * sizeof(float));
// 1. Pipeline, compiled and cached from the engine shader library by name. FPipelineH Pipeline = RHICore.CreateComputePipeline("DoubleNumbers.slang");
// 2. Buffers. Both are CPU-visible (read + write). CPUWrite is the default, // tuned for CPU writes and fast GPU reads; CPURead is tuned for the CPU // reading the GPU's results back (cached reads). Either type can do both. GPUPtr Input = RHI.Malloc(Bytes, EMemoryType.CPUWrite); GPUPtr Output = RHI.Malloc(Bytes, EMemoryType.CPURead);
// Fill the input from the managed array. float* In = (float*)RHI.ToHost(Input); for (int i = 0; i < Count; i++) { In[i] = Values[i]; }
// 3. Args struct in GPU memory, holding the two buffer addresses + the count. // No type given, so this uses the default: CPUWrite, which is read + write. GPUPtr ArgsMem = RHI.Malloc((ulong)sizeof(DoubleArgs)); DoubleArgs* Args = (DoubleArgs*)RHI.ToHost(ArgsMem); Args->Input = Input; Args->Output = Output; Args->Count = (uint)Count;
// 4. Record: bind the global heap (every pipeline's layout includes it), // set the pipeline, dispatch one thread per element (64 per group), // then barrier the compute writes so the CPU can read them. FCmdListH CL = RHI.OpenCommandList(EQueueType.Compute); RHI.CmdSetTextureHeap(CL, RHICore.GetGlobalHeap()); RHI.CmdSetPipeline(CL, Pipeline); RHI.CmdDispatch(CL, ArgsMem, (uint)((Count + 63) / 64), 1, 1); RHI.CmdBarrier(CL, EStageFlags.Compute, EStageFlags.Host);
// 5. Submit, signalling a timeline semaphore, then wait for the GPU. FSemaphoreH Done = RHI.CreateSemaphore(0); FSemaphoreInfo[] Signal = { new FSemaphoreInfo(Done, 1, EStageFlags.Compute) }; RHI.Submit(EQueueType.Compute, new[] { CL }, default, Signal); RHI.WaitSemaphore(Done, 1);
// 6. Read the results back. float[] Result = new float[Count]; float* Out = (float*)RHI.ToHost(Output); for (int i = 0; i < Count; i++) { Result[i] = Out[i]; // Values[i] * 2 }
// 7. Release everything this function created. RHI.Free(Input); RHI.Free(Output); RHI.Free(ArgsMem); RHI.FreeH(Pipeline); RHI.FreeH(Done);
return Result;}That is the full pattern. Every other RHI workload is a variation on it: more
buffers, a graphics pipeline and a render pass instead of a dispatch, or the
results left on the GPU (in GPUOnly memory) for a later pass to consume rather
than read back.
Pipelines
Section titled “Pipelines”A pipeline is the compiled shader plus its fixed-function state. Build a compute or graphics pipeline one of two ways.
By name, from the engine shader library (the path the example uses). The
engine compiles the named .slang through its Slang pipeline and caches the
result, so this is the simplest path for shaders the engine knows about.
FPipelineH Compute = RHICore.CreateComputePipeline("DoubleNumbers.slang");FPipelineH Graphics = RHICore.CreateGraphicsPipeline( "MyVertex.slang", "MyPixel.slang", FRasterDesc.Default, ColorTargets);From compiled bytecode, when you have precompiled SPIR-V to hand. RHI
takes the bytecode and the entry-point name directly:
FPipelineH Pipeline = RHI.CreateComputePipeline(SpirvBytes, "ComputeMain", default);Both compute overloads accept a span of FSpecializationConstant, which bakes
constants into the shader at pipeline-build time (a loop count, a feature
toggle) so the compiler can fold them. Build them with the typed helpers:
FSpecializationConstant[] Constants ={ FSpecializationConstant.UInt(0, 256), FSpecializationConstant.Bool(1, true),};Free a pipeline with RHI.FreeH(Pipeline) when you are done with it.
Mesh shading
Section titled “Mesh shading”Mesh shaders replace the vertex and index pipeline with two compute-like stages: an optional task (amplification) stage, and a mesh stage that emits a small batch of vertices and primitives per workgroup. There is no vertex buffer and no index buffer; the mesh stage produces geometry directly. This suits GPU-driven workloads like meshlet rendering and procedural geometry.
Mesh shaders need recent hardware (NVIDIA Turing and later, AMD RDNA2 and later)
and the VK_EXT_mesh_shader device extension, so always check support first:
if (!RHI.SupportsMeshShaders()){ return; // fall back to a vertex pipeline, or skip the effect}CreateMeshShaderPipeline also returns an invalid handle when mesh shaders are
unsupported, so a guarded call is safe either way.
Creating a mesh pipeline
Section titled “Creating a mesh pipeline”Build a mesh pipeline from compiled bytecode, the same way you build a graphics
pipeline from bytecode. The task stage is optional: pass an empty TaskCode (and
a "" entry) for a mesh-only pipeline, which is the common case. The fragment
stage, raster state, and color targets behave exactly as for a graphics pipeline.
// Mesh-only (no task stage): mesh + fragment.FPipelineH Pipeline = RHI.CreateMeshShaderPipeline( MeshSpirv, "MeshMain", PixelSpirv, "PixelMain", FRasterDesc.Default, ColorTargets, Constants);
// With a task (amplification) stage, use the full overload:FPipelineH WithTask = RHI.CreateMeshShaderPipeline( TaskSpirv, "TaskMain", MeshSpirv, "MeshMain", PixelSpirv, "PixelMain", FRasterDesc.Default, ColorTargets, Constants);Unlike compute and graphics pipelines, there is no named-shader-library overload for mesh pipelines, so supply the compiled SPIR-V yourself.
Dispatching mesh work
Section titled “Dispatching mesh work”A mesh draw is a raster draw, so record it inside a render pass (see below) with
the mesh pipeline bound. You launch a grid of workgroups like a compute dispatch.
The args model is identical to every other draw: DrawArgs is a GPUPtr to your
shader’s argument struct, read back in the shader with GetArgs<T>().
RHI.CmdSetPipeline(CL, Pipeline);RHI.CmdDrawMeshTasks(CL, ArgsPtr, GroupCountX, 1, 1);For GPU-driven counts, drive the workgroup grid from a buffer the GPU filled. The
indirect buffers hold FDrawMeshTasksIndirectArguments records (a uint3
workgroup count, mirroring VkDrawMeshTasksIndirectCommandEXT).
| Call | Launches |
|---|---|
CmdDrawMeshTasks(CL, args, x, y, z) | A fixed x * y * z grid of workgroups. |
CmdDrawMeshTasksIndirect(CL, args, buffer, offset, drawCount, stride) | drawCount draws, each grid read from buffer. |
CmdDrawMeshTasksIndirectCount(CL, args, buffer, offset, countBuffer, countOffset, maxDraws, stride) | As above, with the draw count itself read from countBuffer (capped at maxDraws). |
When you barrier compute or transfer writes before a mesh draw reads them, the
mesh and task stages are already covered by RHI.Barriers.ComputeToAll and
RasterToRead. Name them explicitly with EStageFlags.MeshShader and
EStageFlags.TaskShader when you build a barrier by hand.
Textures
Section titled “Textures”Create a texture from an FTextureDesc. The Texture2D helper fills the common
case; set the EImageUsageFlags for how the texture will be used.
FTextureH Target = RHI.CreateTexture( FTextureDesc.Texture2D(512, 512, EFormat.RGBA16_FLOAT, EImageUsageFlags.Storage | EImageUsageFlags.Sampled));EImageUsageFlags | Allows |
|---|---|
Sampled | Sampling in a shader (a heap sampled slot). |
Storage | Read/write in a shader (a heap storage slot). |
ColorAttachment / DepthAttachment | Rendering into it in a render pass. |
TransferSrc / TransferDst | Copy and blit source or destination. |
To use a texture in a shader, register it in the bindless heap (above) and pass
the returned slot through your args. To render into one, list it as an
attachment in RHI.CmdBeginRenderPass. RHI.CreateTexture(Desc) lets the engine
manage the backing memory; the overload taking a GPUPtr places the texture on
memory you allocated.
Render passes and drawing
Section titled “Render passes and drawing”Raster work (a graphics or mesh pipeline) records inside a render pass that
targets textures you created with ColorAttachment or DepthAttachment usage. A
script renders into its own textures this way; it cannot target the scene view
or the swapchain (see Custom draws).
Begin a pass with the attachments to render into, set the viewport, bind a pipeline, draw, then end the pass.
FRenderAttachment[] Color = { FRenderAttachment.Clear(Target, 0, 0, 0, 1) };
RHI.CmdBeginRenderPass(CL, Color, new FUIntVector2(512, 512)); // color + render areaRHI.CmdSetViewport(CL, FRect.Size(512, 512));RHI.CmdSetScissor(CL, FRect.Size(512, 512));RHI.CmdSetPipeline(CL, Pipeline);RHI.CmdDraw(CL, ArgsPtr, vertexCount: 3, instanceCount: 1, firstVertex: 0, firstInstance: 0);RHI.CmdEndRenderPass(CL);An attachment names a texture and how to treat its existing contents. Pass a
depth attachment to CmdBeginRenderPass for depth testing.
FRenderAttachment | Does |
|---|---|
FRenderAttachment.Clear(tex, r, g, b, a) | Clear to a color first, then store. |
FRenderAttachment.Load(tex) | Keep the existing contents, then store. |
The draw family all take a DrawArgs GPUPtr for the shader args, like a
dispatch:
| Call | Draws |
|---|---|
CmdDraw(CL, args, vertexCount, instanceCount, firstVertex, firstInstance) | Non-indexed. |
CmdDrawIndexed(CL, indexBuffer, indexOffset, args, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance) | Indexed. |
CmdDrawIndirect(CL, args, offset, drawCount, stride) | Args from a buffer of FDrawIndirectArguments. |
CmdDrawIndexedIndirect(CL, args, offset, drawCount, stride) | Indexed, args from a buffer. |
CmdDrawMeshTasks* | Mesh shading (above). |
Set fixed-function state on the command list before drawing:
| Call | Sets |
|---|---|
CmdSetViewport(CL, rect) / CmdSetScissor(CL, rect) | Viewport / scissor rect (an FRect). |
CmdSetCullMode(CL, mode) / CmdSetFrontFace(CL, face) | Face culling and winding. |
CmdSetLineWidth(CL, width) | Line width for line topologies. |
CmdSetIndexBuffer(CL, buffer, offset, type) | The index buffer for indexed draws. |
CmdSetDepthStencilState(CL, ds) | A depth/stencil state object. |
For depth or stencil, create a state object with
RHI.CreateDepthStencil(FDepthStencilDesc), bind it with
CmdSetDepthStencilState, give the pass a depth attachment, and free it with
RHI.FreeH(depthStencil) when done.
Per-frame transient memory
Section titled “Per-frame transient memory”For small, short-lived per-frame data (args structs, a handful of constants),
RHICore.AllocTransient hands out CPU-writable, device-addressable memory from a
ring that the engine recycles automatically each frame. It returns both the CPU
pointer and the GPUPtr, so there is nothing to map and nothing to free.
FTransientAlloc Alloc = RHICore.AllocTransient((ulong)sizeof(DoubleArgs));unsafe{ DoubleArgs* Args = (DoubleArgs*)Alloc.Cpu; Args->Count = 256; // ...}RHI.CmdDispatch(CL, Alloc.Gpu, Groups, 1, 1);The allocation is valid until its frame slot recycles, so use it within the
frame and never hold it across frames. To free a longer-lived allocation safely
once every in-flight frame has retired, use RHICore.DeferredFree(memory)
instead of RHI.Free.
Inspecting the device
Section titled “Inspecting the device”The RHI can report what GPU it is running on and how much memory is in use, which is handy for diagnostics and for sizing allocations.
GPUDeviceInfo Info = RHI.GetDeviceInfo();// Info.Name "NVIDIA GeForce RTX 4080", Info.APIName "Vulkan 1.4.x", Info.IsDiscrete
var (Totals, Heaps) = RHI.GetGPUMemoryStats();// Totals.TotalUsage / Totals.TotalBudget, plus per-heap detail in HeapsCustom draws and the deferred renderer
Section titled “Custom draws and the deferred renderer”Lumina’s renderer is deferred at the frame level. Gameplay runs first, across the update stages (frame start, pre-physics, physics, post-physics, frame end). Only at the end of the frame does the renderer gather (extract) the scene’s state and submit it, and it owns the scene’s render targets and the final present to the window. Every script has already run by the time the scene is drawn.
That ordering shapes what a script can and cannot do with the RHI.
You can run independent GPU work. Anything self-contained, a compute pass, a copy or blit, or a render pass into a texture you created, works from a script. You open a command list, record, submit to a queue, and wait on your own semaphore (the compute example above). It runs as its own GPU submission, on your schedule. This is the intended use of the RHI: GPU computation and offscreen work whose results you own.
You cannot inject a draw into the scene’s view. There is no render-extension or custom-pass hook in the renderer today, and the scene’s color and depth targets, plus the swapchain, are engine-owned and assembled at frame end after every script has run. A command list you submit is its own GPU work; it is not composited into the scene image, and you cannot draw to the backbuffer. Issuing “my own draw call into the world”, the way you would inside an engine render pass, is not possible from a script right now.
To put custom visuals in the world, use the channels that are wired into the renderer:
- For lines, shapes, and text,
World.Draw(debug drawing) feeds the engine’s own debug pass and shows up in the view. See The World API. (Dev and Debug builds.) - For anything heavier, treat the RHI as a producer: compute or render into a texture or buffer you own, then surface that result wherever the engine already lets you reference one. The RHI makes the data; the engine still owns compositing it into the final image.
A general “render into the scene view” extension point (a custom pass or view extension) is a future addition, not something the current API exposes.
Copy, clear, and other commands
Section titled “Copy, clear, and other commands”The rest of the command surface copies and clears memory and textures, and annotates captures. Each records into a command list like the calls above.
Memory:
| Call | Does |
|---|---|
CmdMemcpy(CL, dest, source, size) | GPU-to-GPU copy. |
CmdMemset(CL, dest, size, value) / CmdMemzero(CL, dest, size) | Fill with a value / zero. |
CmdWriteMemory(CL, dest, data) | Inline a CPU byte span into the stream and write it to dest. |
Textures (use FTextureSlice.Full for the whole texture, or set its Mip,
Layer, and Offset/Extent for a sub-region):
| Call | Does |
|---|---|
CmdCopyTexture(CL, src, srcSlice, dst, dstSlice) | Copy a texture region. |
CmdCopyMemoryToTexture(CL, src, rowLength, dst[, slice]) | Upload a buffer into a texture. |
CmdCopyTextureToMemory(CL, src, slice, dst[, rowLength]) | Read a texture back into a buffer. |
CmdBlitTexture(CL, src, srcSlice, dst, dstSlice[, filter]) | Scaled copy (resize, mip generation). |
CmdResolveTexture(CL, src, dst) | Resolve an MSAA texture to a single-sample one. |
CmdClearTexture(CL, tex, r, g, b, a) / CmdClearTextureUInt(CL, tex, r, g, b, a) | Clear to a float / uint color. |
Debug markers group work in a GPU capture (RenderDoc, Nsight):
RHI.CmdBeginMarker(CL, "My pass");// ... record ...RHI.CmdEndMarker(CL);To reuse a command list, RHI.ResetCommandList(CL) clears it for re-recording.
Threading and lifetime, in short
Section titled “Threading and lifetime, in short”- Free what you create. Handles and
GPUPtrare not garbage collected. EveryCreate*,Malloc, andHeapWrite*has a matching free. - Record per-thread, submit synchronized. Build command lists on any thread, including worker threads. Submission and resource creation are the synchronized points.
- Synchronize your own work. Wait on a timeline semaphore you signalled, not on the device. The device and the frame loop belong to the engine.
- Barrier between dependent stages. The GPU will not order a write before a read for you.
If you are doing heavy CPU-side compute rather than GPU work, see Parallel Work. For the high-level, data-driven renderer that most projects use, see Rendering.