deko3d Documentation

repository·master·Indexed 18 days ago

https://github.com/devkitpro/deko3d

A low-level graphics programming API for Nintendo Switch homebrew, inspired by Vulkan and based on reverse-engineered NVN library knowledge. It provides C and C++ interfaces for managing GPU resources, memory blocks, command buffers, and queues. The API supports native GPU code in DKSH format compiled via the uam tool and offers both debug (with validation) and release library versions.

Tokens
9.2K
Snippets
23
Records
25
Agent score
14%

What's inside deko3d

  1. Record and submit commands using DkCmdBuf

    master

    Command buffers (DkCmdBuf) are used to record command lists that are later submitted to a DkQueue.

    Workflow

    1. Create a DkCmdBuf using DkCmdBufMaker.
    2. Provide backing memory via dkCmdBufAddMemory. This memory must be aligned to DK_CMDMEM_ALIGNMENT and its size must be a multiple of DK_CMDMEM_ALIGNMENT. It is recommended to use DkMemBlockFlags_GpuCached | DkMemBlockFlags_CpuUncached.
    3. Record commands. If the buffer runs out of space, the cbAddMem callback in the maker can be used to dynamically add more memory.
    4. Finalize the list with dkCmdBufFinishList, which returns a DkCmdList handle.
    5. Submit the list to a queue using dkQueueSubmitCommands.
    6. Use dkCmdBufClear to destroy all recorded command lists and reset the buffer.

    Advanced Features

    • Command List Reuse: Use dkCmdBufCallList to insert a previously recorded DkCmdList into the current buffer. This allows for efficient sub-command reuse.
    • Multithreading: DkCmdBuf objects are externally synchronized. You should use one command buffer per worker thread and collect the resulting DkCmdList handles in the main thread for submission.

    Memory Requirements

    Memory added via dkCmdBufAddMemory must have CPU-side cache disabled.

    typedef void (*DkCmdBufAddMemFunc)(void* userData, DkCmdBuf cmdbuf, size_t minReqSize);
    struct DkCmdBufMaker
    {
    	DkDevice device;
    	void* userData;
    	DkCmdBufAddMemFunc cbAddMem;
    };
    
    void dkCmdBufMakerDefaults(DkCmdBufMaker* maker, DkDevice device);
    DkCmdBuf dkCmdBufCreate(DkCmdBufMaker const* maker);
    void dkCmdBufDestroy(DkCmdBuf obj);
    void dkCmdBufAddMemory(DkCmdBuf obj, DkMemBlock mem, uint32_t offset, uint32_t size);
    DkCmdList dkCmdBufFinishList(DkCmdBuf obj);
    void dkCmdBufClear(DkCmdBuf obj);
    void dkCmdBufCallList(DkCmdBuf obj, DkCmdList list);
  2. Understand the deko3d object model

    master

    deko3d uses an object-oriented API (available in both C and C++) where objects fall into three distinct categories based on how they manage memory and resources:

    1. Handles: Pointer-sized values that own GPU resources (e.g., DkDevice, DkMemBlock, DkCmdBuf, DkQueue, DkSwapchain). You must explicitly destroy these to free resources, though the C++ wrapper provides Unique types (e.g., dk::UniqueDevice) that handle destruction automatically via RAII.
    2. Opaque objects: Structs containing no public fields that hold internal bookkeeping information (e.g., DkFence, DkShader, DkImage). The user manages the memory for these structs, but they do not own resources and do not need to be destroyed.
    3. Transparent objects: Structs with public fields used to describe mutable hardware state or configuration parameters (e.g., DkViewport, DkColorState, DkBlendState). Objects ending in Maker (e.g., DkDeviceMaker, DkShaderMaker) are specifically used to configure the creation of Handles or the initialization of Opaque objects.
  3. Capture and replay GPU commands

    master

    To avoid CPU bottlenecks caused by converting high-level structs into raw GPU commands every frame, deko3d allows you to capture raw commands into a buffer and replay them with zero computational overhead.

    Workflow

    1. Enter Capture Mode: Call dkCmdBufBeginCaptureCmds with a user-provided storage buffer and max_words.
    2. Record Commands: While in capture mode, dkCmdBuf* calls write directly to the storage buffer instead of the GPU.
    3. End Capture: Call dkCmdBufEndCaptureCmds to stop recording. It returns the total number of captured command words.
    4. Replay: Use dkCmdBufReplayCmds to inject the captured words into any command buffer. This is equivalent to calling the original functions used during capture.

    Limitations and Restrictions

    Capture mode has several constraints. If the storage buffer runs out of space, cbAddMem is ignored and an error occurs. The following operations are disallowed in capture mode:

    • Command Buffer Management: dkCmdBufAddMemory, dkCmdBufFinishList, dkCmdBufClear.
    • Internal Bookkeeping Commands:
      • Compute pipeline configuration/usage.
      • Fence commands (dkCmdBufWaitFence, dkCmdBufSignalFence).
      • Indirect draw/dispatch commands (dkCmdBufDrawIndirect, dkCmdBufDrawIndexedIndirect, dkCmdBufDispatchComputeIndirect).
      • dkCmdBufBarrier with DkBarrier_Full mode.
      • dkCmdBufCallList.
    WARNING

    dkCmdBufReplayCmds allows injecting arbitrary GPU commands. Use with caution. Captured commands may be incompatible if replayed with a different version of deko3d.

    // Example workflow
    // 1. Begin capture
    dkCmdBufBeginCaptureCmds(cmd_buf, storage_buffer, max_words);
    
    // 2. Record commands (e.g., binding, drawing)
    dkCmdBufBindShaders(...);
    dkCmdBufDraw(...);
    
    // 3. End capture
    uint32_t num_words = dkCmdBufEndCaptureCmds(cmd_buf);
    
    // 4. Replay later
    dkCmdBufReplayCmds(other_cmd_buf, storage_buffer, num_words);
  4. Initialize Opaque objects

    master

    Opaque objects are initialized rather than created. This is typically done using a Maker object or by setting fields on a default-initialized struct.

    Using a Maker (e.g., Shaders)

    In C, use dkShaderInitialize. In C++, use the initialize method on the Maker object.

    Direct Initialization (e.g., Samplers)

    Some opaque objects can be initialized by setting fields on a default-initialized struct and then passing them to a descriptor initialization function.

    C++ Example (Shader)

    dk::Shader shader;
    dk::ShaderMaker{codeMemBlock, codeOffset}.initialize(shader);

    C++ Example (Sampler)

    dk::Sampler sampler;
    sampler.setFilter(DkMipFilter_Linear, DkMipFilter_Linear);
    
    dk::SamplerDescriptor descr;
    descr.initialize(sampler);
  5. Create and destroy Handles

    master

    Handles represent owned resources. In C, you use a Maker object to describe the resource and a Create function to instantiate it. In C++, you can use a factory pattern with Maker objects. Handles must be explicitly destroyed unless using a Unique handle type in C++.

    C Example

    // Describe the device we're about to make
    DkDeviceMaker maker;
    dkDeviceMakerDefaults(&maker);
    maker.flags = DkDeviceFlags_OriginLowerLeft;
    
    // Create the device
    DkDevice device = dkDeviceCreate(&maker);
    
    // Destroy the device
    dkDeviceDestroy(device);

    C++ Example

    // Create the device using the factory pattern
    dk::Device device = dk::DeviceMaker{}
    	.setFlags(DkDeviceFlags_OriginLowerLeft)
    	.create();
    
    // Destroy the device
    device.destroy();

    C++ RAII (Automatic Destruction)

    // dk::UniqueDevice automatically calls destroy() when it goes out of scope
    dk::UniqueDevice device = dk::DeviceMaker{}
    	.setFlags(DkDeviceFlags_OriginLowerLeft)
    	.create();
  6. Link to deko3d (Debug vs Release)

    master

    deko3d provides two library versions. Choose based on your current development stage:

    • Debug (libdeko3dd.a): Includes a validation layer with parameter and state checking. Use this during development and experimentation. Link with -ldeko3dd.
    • Release (libdeko3d.a): Optimized and omits validation checks. Use this for shipping production binaries. Link with -ldeko3d.

    In your Makefile, add the appropriate flag to the LIBS section.

  7. Compile and load shaders using DKSH and UAM

    master

    deko3d does not support runtime compilation of GLSL or SPIR-V. It only accepts native GPU code in the DKSH format (Maxwell 2nd gen ISA / SM53).

    The Workflow

    1. Development: Write shaders in GLSL.
    2. Compilation: Use the PC tool uam to compile GLSL to DKSH files.
      uam -o output.dksh -s frag input.glsl
    3. Loading: Load the DKSH file into a DkMemBlock (specifically a block with DkMemBlockFlags_Code).
    4. Initialization: Use dkShaderInitialize to create a DkShader object.

    Loading DKSH Control/Code Sections Separately

    To save GPU memory, you can load the DKSH control section into CPU memory and only the code section into GPU memory:

    1. Parse the DkshHeader from the file.
    2. Load the control_sz section into a temporary CPU buffer.
    3. Load the code_sz section into a DkMemBlock with DkMemBlockFlags_Code.
    4. Set DkShaderMaker.control to the CPU buffer and DkShaderMaker.codeMem/codeOffset to the GPU memory.
    5. Call dkShaderInitialize and free the temporary CPU buffer.

    UAM Compiler Notes

    • Explicit Bindings: You must use layout (binding = N) for UBOs, SSBOs, samplers, and images.
    • Coordinate Systems: gl_FragCoord follows the DkDevice origin flags. layout (origin_upper_left) has no effect.
    • Limitations: Transform feedback, shader subroutines, and shader linking are not supported.
    # Example UAM usage
    uam -o shader.dksh -s vert shader.vert
  8. Configure shaders, buffers, and render targets

    master

    To set up the GPU state for rendering, use these binding functions on a DkCmdBuf:

    Shaders and Buffers

    • dkCmdBufBindShaders: Bind shader stages using a stageMask and an array of DkShader handles.
    • dkCmdBufBindUniformBuffers: Bind uniform buffers to a specific DkStage.
    • dkCmdBufBindStorageBuffers: Bind storage buffers to a specific DkStage.
    • dkCmdBufBindTextures: Bind texture handles to a specific DkStage.
    • dkCmdBufBindImages: Bind image handles to a specific DkStage.
    • dkCmdBufBindImageDescriptorSet: Bind an image descriptor set at a specific DkGpuAddr.
    • dkCmdBufBindSamplerDescriptorSet: Bind a sampler descriptor set at a specific DkGpuAddr.

    Render Targets

    • dkCmdBufBindRenderTargets: Set the color and depth/stencil targets for rendering. Requires an array of DkImageView for color targets and a single DkImageView for the depth target.
    void dkCmdBufBindShaders(DkCmdBuf obj, uint32_t stageMask, DkShader const* const shaders[], uint32_t numShaders);
    void dkCmdBufBindUniformBuffers(DkCmdBuf obj, DkStage stage, uint32_t firstId, DkBufExtents const buffers[], uint32_t numBuffers);
    void dkCmdBufBindStorageBuffers(DkCmdBuf obj, DkStage stage, uint32_t firstId, DkBufExtents const buffers[], uint32_t numBuffers);
    void dkCmdBufBindTextures(DkCmdBuf obj, DkStage stage, uint32_t firstId, DkResHandle const handles[], uint32_t numHandles);
    void dkCmdBufBindImages(DkCmdBuf obj, DkStage stage, uint32_t firstId, DkResHandle const handles[], uint32_t numHandles);
    void dkCmdBufBindImageDescriptorSet(DkCmdBuf obj, DkGpuAddr setAddr, uint32_t numDescriptors);
    void dkCmdBufBindSamplerDescriptorSet(DkCmdBuf obj, DkGpuAddr setAddr, uint32_t numDescriptors);
    void dkCmdBufBindRenderTargets(DkCmdBuf obj, DkImageView const* const colorTargets[], uint32_t numColorTargets, DkImageView const* depthTarget);
  9. Configure the DkDevice for GPU access and error handling

    master

    The DkDevice is the root object representing the GPU device. It manages a private virtual GPU address space and allows customization of memory allocation and error handling.

    Customizing Callbacks

    You can provide custom behavior via the DkDeviceMaker struct:

    • cbError (DkErrorFunc): Handles errors. In debug builds, non-fatal warnings return DkResult_Success. Fatal errors (invalid parameters or unrecoverable GPU errors) return a non-success result and are expected to stop execution.
    • cbAlloc (DkAllocFunc): Custom memory allocation.
    • cbFree (DkFreeFunc): Custom memory deallocation.
    • userData: Pointer to user-defined data passed to all callbacks.

    Device Creation Flags (DkDeviceFlags_*)

    Use these flags to configure coordinate systems and axes:

    • DepthZeroToOne: Clip space Z is [0, 1] (Vulkan/Metal style). Default.
    • DepthMinusOneToOne: Clip space Z is [-1, 1] (OpenGL style).
    • OriginUpperLeft: Image rows from top to bottom. Default.
    • OriginLowerLeft: Image rows from bottom to top.
    • YAxisPointsUp: Clip space Y axis points up. Default.
    • YAxisPointsDown: Clip space Y axis points down.
    struct DkDeviceMaker
    {
    	void* userData;
    	DkErrorFunc cbError;
    	DkAllocFunc cbAlloc;
    	DkFreeFunc cbFree;
    	uint32_t flags;
    };
    
    void dkDeviceMakerDefaults(DkDeviceMaker* maker);
    DkDevice dkDeviceCreate(DkDeviceMaker const* maker);
    void dkDeviceDestroy(DkDevice obj);
  10. Manage memory with DkMemBlock

    master

    A DkMemBlock represents a block of memory used for resources like command lists, shaders, textures, or vertex data.

    Key Configuration Options

    When using DkMemBlockMaker, you can specify:

    • size: Must be a multiple of DK_MEMBLOCK_ALIGNMENT.
    • storage: An optional explicit buffer. If NULL, deko3d allocates it using the device's allocator.
    • flags:
      • Code: Required if the block holds shader code. This places the memory in a special GPU code segment.
      • Image: Required for image data. Creates extra mappings for normal and compressed access.
      • ZeroFillInit: Zero-fills memory on creation.
      • CpuAccessShift / GpuAccessShift: Used with DkMemAccess_* to set visibility/cacheability.

    Memory Access Modes (DkMemAccess_*)

    • None: No CPU or GPU mapping.
    • Uncached: Mapped, but no caching allowed.
    • Cached: Mapped with full cache support.

    Important Usage Notes

    • Code Segment Limitation: Due to a hardware bug, the last DK_SHADER_CODE_UNUSABLE_SIZE bytes of a block are unusable for shader code. Also, deko3d cannot currently reuse mappings in the code segment after they are freed; it is better to reuse old code memory blocks.
    • CPU/GPU Synchronization: For CpuCached memory, use dkMemBlockFlushCpuCache to make CPU writes visible to the GPU. Avoid using CpuCached for GPU $\rightarrow$ CPU communication as there is no support for invalidating the CPU-side cache.
    struct DkMemBlockMaker
    {
    	DkDevice device;
    	uint32_t size;
    	uint32_t flags;
    	void* storage;
    };
    
    void dkMemBlockMakerDefaults(DkMemBlockMaker* maker, DkDevice device, uint32_t size);
    DkMemBlock dkMemBlockCreate(DkMemBlockMaker const* maker);
    void dkMemBlockDestroy(DkMemBlock obj);
    void* dkMemBlockGetCpuAddr(DkMemBlock obj);
    DkGpuAddr dkMemBlockGetGpuAddr(DkMemBlock obj);
    uint32_t dkMemBlockGetSize(DkMemBlock obj);
    DkResult dkMemBlockFlushCpuCache(DkMemBlock obj, uint32_t offset, uint32_t size);
  11. Specify vertex attributes and buffers

    master

    Before drawing, you must bind the vertex attribute state, vertex buffers, and optionally an index buffer to the command buffer.

    // Bind attribute layout
    void dkCmdBufBindVtxAttribState(DkCmdBuf obj, DkVtxAttribState const attribs[], uint32_t numAttribs);
    
    // Bind vertex buffers
    void dkCmdBufBindVtxBufferState(DkCmdBuf obj, DkVtxBufferState const buffers[], uint32_t numBuffers);
    void dkCmdBufBindVtxBuffers(DkCmdBuf obj, uint32_t firstId, DkBufExtents const buffers[], uint32_t numBuffers);
    
    // Bind index buffer
    void dkCmdBufBindIdxBuffer(DkCmdBuf obj, DkIdxFormat format, DkGpuAddr address);
    
    // Enable primitive restart
    void dkCmdBufSetPrimitiveRestart(DkCmdBuf obj, bool enable, uint32_t index);
  12. Configure sample output (Blending and Write Masks)

    master

    Set how fragments are written to the color buffer, including blending modes and color write masks.

    void dkCmdBufBindColorState(DkCmdBuf obj, DkColorState const* state);
    void dkCmdBufBindBlendStates(DkCmdBuf obj, uint32_t firstId, DkBlendState const states[], uint32_t numStates);
    void dkCmdBufBindColorWriteState(DkCmdBuf obj, DkColorWriteState const* state);
    void dkCmdBufSetBlendConst(DkCmdBuf obj, float red, float green, float blue, float alpha);