#pragma once #include #include #ifdef __cplusplus #include #endif #if !defined(DUSK_BUILDING_GAME) && !defined(DUSK_MOD_FEATURE_WEBGPU) #error "mods/svc/gfx.h requires add_mod(... FEATURES webgpu)" #endif #include /* * Direct WebGPU access at various stages of the rendering pipeline. Mods use the wgpu* C API * (via webgpu/webgpu.h) for custom draws and compute dispatches. * * Every service function must be called on the game thread. GfxStageFn callbacks run on the game * thread during frame recording. push_draw, push_* and pass functions are valid from a stage * callback and anywhere else GX commands are being recorded. * * GfxDrawFn and GfxComputeFn callbacks run on the render worker thread while the frame is encoded. * They may use only the handles in their context struct and raw wgpu* calls; no other service may * be called from them. * * All WGPU handles provided by this service are borrowed. Handles in callback contexts are valid * only for the duration of the callback; views in GfxResolvedTargets are valid for the current * frame only. GPU objects a mod creates through raw wgpu calls are its own responsibility and * should be released in mod_shutdown. The device outlives all mods. */ #define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx" #define GFX_SERVICE_MAJOR 1u #define GFX_SERVICE_MINOR 1u /* Maximum size for push_draw payload */ #define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u /* 0 is never a valid handle. */ typedef uint64_t GfxDrawTypeHandle; typedef uint64_t GfxStageHookHandle; typedef uint64_t GfxComputeTypeHandle; typedef uint64_t GfxPresentTargetHandle; /* A suballocation in one of the shared per-frame streaming buffers. */ typedef struct GfxRange { uint32_t offset; uint32_t size; } GfxRange; /* * Device and scene pass configuration. Valid from mod_initialize onward and stable for the * session. Offscreen passes from create_pass are always single-sample. */ typedef struct GfxDeviceInfo { uint32_t struct_size; WGPUDevice device; /* borrowed */ WGPUQueue queue; /* borrowed */ WGPUTextureFormat color_format; /* scene color target format */ WGPUTextureFormat depth_format; /* scene depth target format */ uint32_t sample_count; /* scene pass MSAA sample count */ bool uses_reversed_z; /* true means depth 1.0 is near */ WGPUInstance instance; /* borrowed; added in GfxService 1.1 */ WGPUAdapter adapter; /* borrowed; added in GfxService 1.1 */ } GfxDeviceInfo; #define GFX_DEVICE_INFO_INIT \ {sizeof(GfxDeviceInfo), NULL, NULL, WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined, \ 1u, false, NULL, NULL} /* * Passed to GfxDrawFn on the render worker thread; valid only during the call. The pass pipeline, * bind group, viewport, and scissor state is restored by the host after the callback returns. */ typedef struct GfxDrawContext { uint32_t struct_size; WGPUDevice device; WGPUQueue queue; WGPURenderPassEncoder pass; WGPUBuffer vertex_buffer; WGPUBuffer index_buffer; WGPUBuffer uniform_buffer; WGPUBuffer storage_buffer; WGPUTextureFormat color_format; WGPUTextureFormat depth_format; uint32_t sample_count; uint32_t target_width; uint32_t target_height; bool uses_reversed_z; } GfxDrawContext; typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload, size_t payload_size, void* user_data); typedef struct GfxDrawTypeDesc { uint32_t struct_size; const char* label; /* optional debug label */ GfxDrawFn draw; /* required; called from the render worker thread */ void* user_data; } GfxDrawTypeDesc; #define GFX_DRAW_TYPE_DESC_INIT {sizeof(GfxDrawTypeDesc), NULL, NULL, NULL} typedef enum GfxStage { GFX_STAGE_SCENE_AFTER_TERRAIN = 0, GFX_STAGE_FRAME_BEFORE_HUD = 1, GFX_STAGE_FRAME_AFTER_HUD = 2, GFX_STAGE_SCENE_BEGIN = 3, GFX_STAGE_SCENE_AFTER_OPAQUE = 4, } GfxStage; typedef struct GfxStageContext { uint32_t struct_size; GfxStage stage; const void* game_view; /* view_class* for world-camera stages; NULL otherwise */ const void* game_viewport; /* view_port_class* for world-camera stages; NULL otherwise */ } GfxStageContext; typedef void (*GfxStageFn)(ModContext* ctx, const GfxStageContext* stage_ctx, void* user_data); typedef struct GfxStageHookDesc { uint32_t struct_size; GfxStageFn callback; /* required */ void* user_data; } GfxStageHookDesc; #define GFX_STAGE_HOOK_DESC_INIT {sizeof(GfxStageHookDesc), NULL, NULL} typedef struct GfxResolveDesc { uint32_t struct_size; bool color; bool depth; } GfxResolveDesc; #define GFX_RESOLVE_DESC_INIT {sizeof(GfxResolveDesc), true, false} typedef struct GfxResolvedTargets { uint32_t struct_size; WGPUTextureView color; /* single-sample snapshot in color_format */ WGPUTextureView depth; /* single-sample raw depth snapshot, R32Float when available */ WGPUTextureFormat color_format; uint32_t width; uint32_t height; } GfxResolvedTargets; #define GFX_RESOLVED_TARGETS_INIT \ {sizeof(GfxResolvedTargets), NULL, NULL, WGPUTextureFormat_Undefined, 0u, 0u} /* * Passed to GfxComputeFn on the render worker thread; valid only during the call. The encoder is * the frame command encoder between scene render passes. Leave no pass open and never finish or * release the encoder. */ typedef struct GfxComputeContext { uint32_t struct_size; WGPUDevice device; WGPUQueue queue; WGPUCommandEncoder encoder; WGPUBuffer vertex_buffer; WGPUBuffer index_buffer; WGPUBuffer uniform_buffer; WGPUBuffer storage_buffer; } GfxComputeContext; typedef void (*GfxComputeFn)(ModContext* ctx, const GfxComputeContext* compute_ctx, const void* payload, size_t payload_size, void* user_data); typedef struct GfxComputeTypeDesc { uint32_t struct_size; const char* label; /* optional debug label */ GfxComputeFn callback; /* required; called from the render worker thread */ void* user_data; } GfxComputeTypeDesc; #define GFX_COMPUTE_TYPE_DESC_INIT {sizeof(GfxComputeTypeDesc), NULL, NULL, NULL} /* * Invoked on the render worker while the frame encoder is open. The target texture and view have * been acquired by the host and are borrowed for the callback. Record all target work on encoder, * leave no pass open, and do not finish, submit, or present it. The host submits the shared command * buffer and presents the target after submission. The streaming buffers contain data appended on * the game thread before push_present. */ typedef struct GfxPresentContext { uint32_t struct_size; WGPUDevice device; WGPUQueue queue; WGPUCommandEncoder encoder; WGPUTexture target_texture; WGPUTextureView target_view; WGPUTextureFormat target_format; uint32_t target_width; uint32_t target_height; WGPUBuffer vertex_buffer; WGPUBuffer index_buffer; WGPUBuffer uniform_buffer; WGPUBuffer storage_buffer; } GfxPresentContext; typedef void (*GfxPresentFn)(ModContext* ctx, const GfxPresentContext* present_ctx, const void* payload, size_t payload_size, void* user_data); typedef struct GfxPresentTargetDesc { uint32_t struct_size; const char* label; /* optional debug label */ uint32_t width; /* required for raw surfaces; ignored for WindowService windows */ uint32_t height; WGPUTextureUsage usage; /* 0 defaults to RenderAttachment */ WGPUTextureFormat preferred_format; WGPUCompositeAlphaMode preferred_alpha_mode; GfxPresentFn render; void* user_data; } GfxPresentTargetDesc; #define GFX_PRESENT_TARGET_DESC_INIT \ {sizeof(GfxPresentTargetDesc), NULL, 0u, 0u, WGPUTextureUsage_None, \ WGPUTextureFormat_Undefined, WGPUCompositeAlphaMode_Auto, NULL, NULL} typedef struct GfxService { ServiceHeader header; ModResult (*get_device_info)(ModContext* ctx, GfxDeviceInfo* out_info); void* (*get_proc_address)(ModContext* ctx, const char* name); ModResult (*register_draw_type)( ModContext* ctx, const GfxDrawTypeDesc* desc, GfxDrawTypeHandle* out_handle); ModResult (*unregister_draw_type)(ModContext* ctx, GfxDrawTypeHandle handle); ModResult (*push_draw)( ModContext* ctx, GfxDrawTypeHandle handle, const void* payload, size_t payload_size); ModResult (*register_compute_type)( ModContext* ctx, const GfxComputeTypeDesc* desc, GfxComputeTypeHandle* out_handle); ModResult (*unregister_compute_type)(ModContext* ctx, GfxComputeTypeHandle handle); ModResult (*push_compute)( ModContext* ctx, GfxComputeTypeHandle handle, const void* payload, size_t payload_size); ModResult (*push_verts)( ModContext* ctx, const void* data, size_t size, size_t alignment, GfxRange* out_range); ModResult (*push_indices)( ModContext* ctx, const void* data, size_t size, size_t alignment, GfxRange* out_range); ModResult (*push_uniform)(ModContext* ctx, const void* data, size_t size, GfxRange* out_range); ModResult (*push_storage)(ModContext* ctx, const void* data, size_t size, GfxRange* out_range); ModResult (*register_stage_hook)(ModContext* ctx, GfxStage stage, const GfxStageHookDesc* desc, GfxStageHookHandle* out_handle); ModResult (*unregister_stage_hook)(ModContext* ctx, GfxStageHookHandle handle); ModResult (*resolve_pass)( ModContext* ctx, const GfxResolveDesc* desc, GfxResolvedTargets* out_targets); ModResult (*create_pass)(ModContext* ctx, uint32_t width, uint32_t height); /* Minor version 1 */ ModResult (*register_present_target)(ModContext* ctx, WGPUSurface surface, const GfxPresentTargetDesc* desc, GfxPresentTargetHandle* out_handle); ModResult (*register_window_present_target)(ModContext* ctx, WindowHandle window, const GfxPresentTargetDesc* desc, GfxPresentTargetHandle* out_handle); /* Raw-surface targets only; WindowService target resizes are managed automatically. */ ModResult (*resize_present_target)( ModContext* ctx, GfxPresentTargetHandle handle, uint32_t width, uint32_t height); ModResult (*unregister_present_target)(ModContext* ctx, GfxPresentTargetHandle handle); /* * MOD_OK means the task was queued. * MOD_UNAVAILABLE means no task could be queued now (for example, a window has no pixel size). * MOD_ERROR means an earlier task found the surface lost or deterministically invalid; * unregister and recreate the target before pushing again. */ ModResult (*push_present)( ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size); } GfxService; MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR);