#include "opengl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib, "dxcompiler.lib") #pragma comment(lib, "d3dcompiler.lib") using Microsoft::WRL::ComPtr; // ============================================================ // Logging / checks // ============================================================ static void glRaytracingLog(const char* fmt, ...) { char buffer[4096]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); OutputDebugStringA(buffer); OutputDebugStringA("\n"); } static void glRaytracingFatal(const char* fmt, ...) { char buffer[4096]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); OutputDebugStringA(buffer); OutputDebugStringA("\n"); MessageBoxA(nullptr, buffer, "glRaytracing Fatal", MB_OK | MB_ICONERROR); DebugBreak(); } #define GLR_CHECK(x) \ do { HRESULT _hr = (x); if (FAILED(_hr)) { glRaytracingFatal("HRESULT 0x%08X failed at %s:%d", (unsigned)_hr, __FILE__, __LINE__); return 0; } } while (0) #define GLR_CHECKV(x) \ do { HRESULT _hr = (x); if (FAILED(_hr)) { glRaytracingFatal("HRESULT 0x%08X failed at %s:%d", (unsigned)_hr, __FILE__, __LINE__); return; } } while (0) // ============================================================ // Helpers // ============================================================ static UINT64 glRaytracingAlignUp(UINT64 v, UINT64 a) { return (v + (a - 1)) & ~(a - 1); } template static T glRaytracingClamp(T v, T lo, T hi) { return (v < lo) ? lo : ((v > hi) ? hi : v); } static DXGI_FORMAT glRaytracingGetSrvFormatForDepth(DXGI_FORMAT fmt) { switch (fmt) { case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_FLOAT; case DXGI_FORMAT_D24_UNORM_S8_UINT: return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_UNORM; default: return fmt; } } struct glRaytracingBuffer_t { ComPtr resource; UINT64 size; D3D12_GPU_VIRTUAL_ADDRESS gpuVA; glRaytracingBuffer_t() { size = 0; gpuVA = 0; } }; static glRaytracingBuffer_t glRaytracingCreateBuffer( ID3D12Device* device, UINT64 size, D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES initialState, D3D12_RESOURCE_FLAGS flags) { glRaytracingBuffer_t out; D3D12_HEAP_PROPERTIES hp = {}; hp.Type = heapType; D3D12_RESOURCE_DESC rd = {}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = size; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.Format = DXGI_FORMAT_UNKNOWN; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; rd.Flags = flags; HRESULT hr = device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, initialState, nullptr, IID_PPV_ARGS(&out.resource)); if (FAILED(hr)) { glRaytracingFatal("CreateCommittedResource failed 0x%08X", (unsigned)hr); return out; } out.size = size; out.gpuVA = out.resource->GetGPUVirtualAddress(); return out; } static void glRaytracingMapCopy(ID3D12Resource* res, const void* src, size_t bytes) { void* dst = nullptr; HRESULT hr = res->Map(0, nullptr, &dst); if (FAILED(hr)) { glRaytracingFatal("Map failed 0x%08X", (unsigned)hr); return; } memcpy(dst, src, bytes); res->Unmap(0, nullptr); } static void glRaytracingTransition( ID3D12GraphicsCommandList* cmd, ID3D12Resource* res, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) { if (!res || before == after) return; D3D12_RESOURCE_BARRIER b = {}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = res; b.Transition.StateBefore = before; b.Transition.StateAfter = after; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; cmd->ResourceBarrier(1, &b); } static D3D12_CPU_DESCRIPTOR_HANDLE glRaytracingOffsetCpu(D3D12_CPU_DESCRIPTOR_HANDLE h, UINT stride, UINT idx) { h.ptr += UINT64(stride) * UINT64(idx); return h; } static D3D12_GPU_DESCRIPTOR_HANDLE glRaytracingOffsetGpu(D3D12_GPU_DESCRIPTOR_HANDLE h, UINT stride, UINT idx) { h.ptr += UINT64(stride) * UINT64(idx); return h; } // ============================================================ // Shared command context // ============================================================ struct glRaytracingCmdContext_t { ComPtr device; ComPtr queue; ComPtr cmdAlloc; ComPtr cmdList; UINT64 cmdLastFenceValue; ComPtr blasCmdAlloc; ComPtr blasCmdList; UINT64 blasLastFenceValue; ComPtr tlasCmdAlloc; ComPtr tlasCmdList; UINT64 tlasLastFenceValue; ComPtr fence; HANDLE fenceEvent; UINT64 nextFenceValue; bool initialized; glRaytracingCmdContext_t() { cmdLastFenceValue = 0; blasLastFenceValue = 0; tlasLastFenceValue = 0; fenceEvent = nullptr; nextFenceValue = 0; initialized = false; } }; static glRaytracingCmdContext_t g_glRaytracingCmd; static std::mutex g_glRaytracingMutex; static void glRaytracingWaitFenceValue(UINT64 value) { if (!value || !g_glRaytracingCmd.fence) return; if (g_glRaytracingCmd.fence->GetCompletedValue() >= value) return; g_glRaytracingCmd.fence->SetEventOnCompletion(value, g_glRaytracingCmd.fenceEvent); WaitForSingleObject(g_glRaytracingCmd.fenceEvent, INFINITE); } static UINT64 glRaytracingSignalQueue(void) { if (!g_glRaytracingCmd.queue || !g_glRaytracingCmd.fence) return 0; const UINT64 value = ++g_glRaytracingCmd.nextFenceValue; g_glRaytracingCmd.queue->Signal(g_glRaytracingCmd.fence.Get(), value); return value; } static void glRaytracingWaitIdle(void) { const UINT64 value = glRaytracingSignalQueue(); glRaytracingWaitFenceValue(value); } static int glRaytracingInitCmdContext(void) { ID3D12Device* baseDevice = QD3D12_GetDevice(); ID3D12CommandQueue* baseQueue = QD3D12_GetQueue(); if (g_glRaytracingCmd.initialized) { if (!baseDevice || !baseQueue) { glRaytracingFatal("glRaytracingInitCmdContext: missing device or queue"); return 0; } ComPtr currentDevice; HRESULT hr = baseDevice->QueryInterface(IID_PPV_ARGS(¤tDevice)); if (FAILED(hr) || currentDevice.Get() != g_glRaytracingCmd.device.Get() || baseQueue != g_glRaytracingCmd.queue.Get()) { glRaytracingFatal("glRaytracingInitCmdContext: D3D12 device/queue changed. DXR state is device-local; create/use all windows with the same D3D12 device and queue, or fully shut down raytracing before switching devices."); return 0; } return 1; } if (!baseDevice || !baseQueue) { glRaytracingFatal("glRaytracingInitCmdContext: missing device or queue"); return 0; } GLR_CHECK(baseDevice->QueryInterface(IID_PPV_ARGS(&g_glRaytracingCmd.device))); g_glRaytracingCmd.queue = baseQueue; GLR_CHECK(g_glRaytracingCmd.device->CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_glRaytracingCmd.cmdAlloc))); GLR_CHECK(g_glRaytracingCmd.device->CreateCommandList( 0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_glRaytracingCmd.cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&g_glRaytracingCmd.cmdList))); GLR_CHECK(g_glRaytracingCmd.cmdList->Close()); GLR_CHECK(g_glRaytracingCmd.device->CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_glRaytracingCmd.blasCmdAlloc))); GLR_CHECK(g_glRaytracingCmd.device->CreateCommandList( 0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_glRaytracingCmd.blasCmdAlloc.Get(), nullptr, IID_PPV_ARGS(&g_glRaytracingCmd.blasCmdList))); GLR_CHECK(g_glRaytracingCmd.blasCmdList->Close()); GLR_CHECK(g_glRaytracingCmd.device->CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_glRaytracingCmd.tlasCmdAlloc))); GLR_CHECK(g_glRaytracingCmd.device->CreateCommandList( 0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_glRaytracingCmd.tlasCmdAlloc.Get(), nullptr, IID_PPV_ARGS(&g_glRaytracingCmd.tlasCmdList))); GLR_CHECK(g_glRaytracingCmd.tlasCmdList->Close()); GLR_CHECK(g_glRaytracingCmd.device->CreateFence( 0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_glRaytracingCmd.fence))); g_glRaytracingCmd.fenceEvent = CreateEventA(nullptr, FALSE, FALSE, nullptr); if (!g_glRaytracingCmd.fenceEvent) { glRaytracingFatal("CreateEventA failed"); return 0; } g_glRaytracingCmd.initialized = true; return 1; } static void glRaytracingShutdownCmdContext(void) { if (!g_glRaytracingCmd.initialized) return; glRaytracingWaitIdle(); if (g_glRaytracingCmd.fenceEvent) { CloseHandle(g_glRaytracingCmd.fenceEvent); g_glRaytracingCmd.fenceEvent = nullptr; } g_glRaytracingCmd = glRaytracingCmdContext_t(); } static int glRaytracingBeginCmd(void) { glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdLastFenceValue); GLR_CHECK(g_glRaytracingCmd.cmdAlloc->Reset()); GLR_CHECK(g_glRaytracingCmd.cmdList->Reset(g_glRaytracingCmd.cmdAlloc.Get(), nullptr)); return 1; } static int glRaytracingEndCmd(void) { GLR_CHECK(g_glRaytracingCmd.cmdList->Close()); ID3D12CommandList* lists[] = { g_glRaytracingCmd.cmdList.Get() }; g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists); g_glRaytracingCmd.cmdLastFenceValue = glRaytracingSignalQueue(); glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdLastFenceValue); return 1; } static int glRaytracingBeginBlasCmd(void) { glRaytracingWaitFenceValue(g_glRaytracingCmd.blasLastFenceValue); GLR_CHECK(g_glRaytracingCmd.blasCmdAlloc->Reset()); GLR_CHECK(g_glRaytracingCmd.blasCmdList->Reset(g_glRaytracingCmd.blasCmdAlloc.Get(), nullptr)); return 1; } static UINT64 glRaytracingEndBlasCmd(void) { GLR_CHECK(g_glRaytracingCmd.blasCmdList->Close()); ID3D12CommandList* lists[] = { g_glRaytracingCmd.blasCmdList.Get() }; g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists); g_glRaytracingCmd.blasLastFenceValue = glRaytracingSignalQueue(); return g_glRaytracingCmd.blasLastFenceValue; } static int glRaytracingBeginTlasCmd(void) { glRaytracingWaitFenceValue(g_glRaytracingCmd.tlasLastFenceValue); GLR_CHECK(g_glRaytracingCmd.tlasCmdAlloc->Reset()); GLR_CHECK(g_glRaytracingCmd.tlasCmdList->Reset(g_glRaytracingCmd.tlasCmdAlloc.Get(), nullptr)); return 1; } static UINT64 glRaytracingEndTlasCmd(void) { GLR_CHECK(g_glRaytracingCmd.tlasCmdList->Close()); ID3D12CommandList* lists[] = { g_glRaytracingCmd.tlasCmdList.Get() }; g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists); g_glRaytracingCmd.tlasLastFenceValue = glRaytracingSignalQueue(); return g_glRaytracingCmd.tlasLastFenceValue; } // ============================================================ // Scene builder state // ============================================================ #ifndef GL_RAYTRACING_MAX_RENDER_WORLDS #define GL_RAYTRACING_MAX_RENDER_WORLDS 24 #endif #ifndef GL_RAYTRACING_SCENE_HANDLE_T_DEFINED typedef uint32_t glRaytracingSceneHandle_t; #define GL_RAYTRACING_SCENE_HANDLE_T_DEFINED #endif struct glRaytracingMeshRecord_t { uint32_t handle; int alive; glRaytracingMeshDesc_t descCpu; std::vector verticesCpu; std::vector indicesCpu; glRaytracingBuffer_t vertexBuffer; glRaytracingBuffer_t indexBuffer; glRaytracingBuffer_t blasScratch; glRaytracingBuffer_t blasResult[2]; UINT64 blasScratchSize; UINT64 blasResultSize; int blasBuilt; int dirty; int currentBlasIndex; glRaytracingMeshRecord_t() { handle = 0; alive = 0; memset(&descCpu, 0, sizeof(descCpu)); blasScratchSize = 0; blasResultSize = 0; blasBuilt = 0; dirty = 0; currentBlasIndex = 0; } }; struct glRaytracingInstanceRecord_t { uint32_t handle; int alive; glRaytracingInstanceDesc_t descCpu; int dirty; int cachedActive; D3D12_GPU_VIRTUAL_ADDRESS cachedBlasGpuVA; D3D12_RAYTRACING_INSTANCE_DESC cachedDescCpu; glRaytracingInstanceRecord_t() { handle = 0; alive = 0; memset(&descCpu, 0, sizeof(descCpu)); dirty = 0; cachedActive = 0; cachedBlasGpuVA = 0; memset(&cachedDescCpu, 0, sizeof(cachedDescCpu)); } }; struct glRaytracingSceneUploadBuffer_t { glRaytracingBuffer_t buffer; UINT64 capacityBytes; D3D12_RAYTRACING_INSTANCE_DESC* mapped; glRaytracingSceneUploadBuffer_t() { capacityBytes = 0; mapped = nullptr; } }; // One render world owns exactly one TLAS pair and its own list of geometry // instances. Mesh/BLAS resources stay shared across all worlds. struct glRaytracingRenderWorld_t { uint32_t handle; int alive; std::vector instances; std::vector activeInstanceIndices; std::vector cpuInstanceDescs; std::vector instanceHandleToIndex; uint32_t nextInstanceHandle; glRaytracingSceneUploadBuffer_t instanceDescUpload[2]; glRaytracingBuffer_t tlasScratch; glRaytracingBuffer_t tlasResult[2]; UINT64 tlasScratchSize; UINT64 tlasResultSize; UINT activeInstanceCount; UINT builtInstanceCount; int tlasBuilt; int tlasNeedsRebuild; int tlasNeedsUpdate; int currentTLASIndex; glRaytracingRenderWorld_t() { handle = 0; alive = 0; nextInstanceHandle = 1; tlasScratchSize = 0; tlasResultSize = 0; activeInstanceCount = 0; builtInstanceCount = 0; tlasBuilt = 0; tlasNeedsRebuild = 1; tlasNeedsUpdate = 1; currentTLASIndex = 0; } }; struct glRaytracingSceneState_t { std::vector meshes; std::vector meshHandleToIndex; uint32_t nextMeshHandle; glRaytracingRenderWorld_t worlds[GL_RAYTRACING_MAX_RENDER_WORLDS]; int initialized; glRaytracingSceneState_t() { nextMeshHandle = 1; initialized = 0; } }; static glRaytracingSceneState_t g_glRaytracingScene; void glRaytracingClear(void); static void glRaytracingReleaseWorldResources(glRaytracingRenderWorld_t* world) { if (!world) return; for (int i = 0; i < 2; ++i) { if (world->instanceDescUpload[i].buffer.resource && world->instanceDescUpload[i].mapped) world->instanceDescUpload[i].buffer.resource->Unmap(0, nullptr); world->instanceDescUpload[i] = glRaytracingSceneUploadBuffer_t(); world->tlasResult[i].resource.Reset(); } world->tlasScratch.resource.Reset(); world->tlasScratchSize = 0; world->tlasResultSize = 0; } static void glRaytracingResetWorldSlot(glRaytracingRenderWorld_t* world, uint32_t handle, int alive) { if (!world) return; glRaytracingReleaseWorldResources(world); *world = glRaytracingRenderWorld_t(); world->handle = handle; world->alive = alive ? 1 : 0; world->nextInstanceHandle = 1; } static int glRaytracingWorldHandleToSlot(glRaytracingSceneHandle_t worldHandle) { if (worldHandle == 0 || worldHandle > GL_RAYTRACING_MAX_RENDER_WORLDS) return -1; return (int)(worldHandle - 1); } static glRaytracingRenderWorld_t* glRaytracingFindWorld(glRaytracingSceneHandle_t worldHandle) { const int slot = glRaytracingWorldHandleToSlot(worldHandle); if (slot < 0) return nullptr; glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[slot]; if (!world.alive || world.handle != worldHandle) return nullptr; return &world; } static const glRaytracingRenderWorld_t* glRaytracingFindWorldConst(glRaytracingSceneHandle_t worldHandle) { const int slot = glRaytracingWorldHandleToSlot(worldHandle); if (slot < 0) return nullptr; const glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[slot]; if (!world.alive || world.handle != worldHandle) return nullptr; return &world; } static void glRaytracingClearWorldContents(glRaytracingRenderWorld_t* world) { if (!world) return; const uint32_t handle = world->handle; const int alive = world->alive; glRaytracingReleaseWorldResources(world); world->instances.clear(); world->activeInstanceIndices.clear(); world->cpuInstanceDescs.clear(); world->instanceHandleToIndex.clear(); world->handle = handle; world->alive = alive; world->nextInstanceHandle = 1; world->tlasScratchSize = 0; world->tlasResultSize = 0; world->activeInstanceCount = 0; world->builtInstanceCount = 0; world->tlasBuilt = 0; world->tlasNeedsRebuild = 1; world->tlasNeedsUpdate = 1; world->currentTLASIndex = 0; } static void glRaytracingClearAllSceneStateInternal(void) { for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i) glRaytracingReleaseWorldResources(&g_glRaytracingScene.worlds[i]); const int wasInitialized = g_glRaytracingScene.initialized; g_glRaytracingScene = glRaytracingSceneState_t(); g_glRaytracingScene.initialized = wasInitialized; } static void glRaytracingMarkWorldNeedsRebuild(glRaytracingRenderWorld_t* world) { if (!world || !world->alive) return; world->tlasNeedsRebuild = 1; world->tlasNeedsUpdate = 0; } static void glRaytracingMarkWorldNeedsUpdate(glRaytracingRenderWorld_t* world) { if (!world || !world->alive) return; if (!world->tlasNeedsRebuild) world->tlasNeedsUpdate = 1; } static void glRaytracingMarkAllWorldsNeedRebuild(void) { for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i) { if (g_glRaytracingScene.worlds[i].alive) glRaytracingMarkWorldNeedsRebuild(&g_glRaytracingScene.worlds[i]); } } static uint32_t glRaytracingCountAliveInstances(const glRaytracingRenderWorld_t* world) { if (!world) return 0; uint32_t count = 0; for (size_t i = 0; i < world->instances.size(); ++i) { if (world->instances[i].alive) ++count; } return count; } static void glRaytracingEnsureMeshHandleTable(uint32_t handle) { if (handle >= g_glRaytracingScene.meshHandleToIndex.size()) g_glRaytracingScene.meshHandleToIndex.resize((size_t)handle + 1, -1); } static void glRaytracingEnsureInstanceHandleTable(glRaytracingRenderWorld_t* world, uint32_t handle) { if (!world) return; if (handle >= world->instanceHandleToIndex.size()) world->instanceHandleToIndex.resize((size_t)handle + 1, -1); } static glRaytracingBuffer_t* glRaytracingGetMeshCurrentBLAS(glRaytracingMeshRecord_t* mesh) { if (!mesh) return nullptr; return &mesh->blasResult[mesh->currentBlasIndex & 1]; } static const glRaytracingBuffer_t* glRaytracingGetMeshCurrentBLASConst(const glRaytracingMeshRecord_t* mesh) { if (!mesh) return nullptr; return &mesh->blasResult[mesh->currentBlasIndex & 1]; } static int glRaytracingGetInactiveTLASIndex(const glRaytracingRenderWorld_t* world) { if (!world) return 0; return world->currentTLASIndex ^ 1; } static glRaytracingSceneUploadBuffer_t* glRaytracingGetBuildInstanceUpload(glRaytracingRenderWorld_t* world) { return &world->instanceDescUpload[glRaytracingGetInactiveTLASIndex(world)]; } static glRaytracingBuffer_t* glRaytracingGetCurrentTLASBuffer(glRaytracingRenderWorld_t* world) { return &world->tlasResult[world->currentTLASIndex & 1]; } static const glRaytracingBuffer_t* glRaytracingGetCurrentTLASBufferConst(const glRaytracingRenderWorld_t* world) { return &world->tlasResult[world->currentTLASIndex & 1]; } static glRaytracingBuffer_t* glRaytracingGetBuildTLASBuffer(glRaytracingRenderWorld_t* world) { return &world->tlasResult[glRaytracingGetInactiveTLASIndex(world)]; } static int glRaytracingEnsureTLASBuffers( glRaytracingRenderWorld_t* world, const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS* inputs) { if (!world) return 0; D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuild = {}; g_glRaytracingCmd.device->GetRaytracingAccelerationStructurePrebuildInfo(inputs, &prebuild); if (prebuild.ResultDataMaxSizeInBytes == 0) { glRaytracingFatal("TLAS prebuild size is zero"); return 0; } const UINT64 requiredScratch = glRaytracingAlignUp( prebuild.ScratchDataSizeInBytes, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT); const UINT64 requiredResult = glRaytracingAlignUp( prebuild.ResultDataMaxSizeInBytes, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT); if (!world->tlasScratch.resource || world->tlasScratchSize < requiredScratch) { world->tlasScratch.resource.Reset(); world->tlasScratch = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), requiredScratch, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); if (!world->tlasScratch.resource) return 0; world->tlasScratchSize = requiredScratch; } for (int i = 0; i < 2; ++i) { if (!world->tlasResult[i].resource || world->tlasResultSize < requiredResult) { world->tlasResult[i].resource.Reset(); world->tlasResult[i] = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), requiredResult, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); if (!world->tlasResult[i].resource) return 0; } } world->tlasResultSize = requiredResult; return 1; } static glRaytracingMeshRecord_t* glRaytracingFindMesh(uint32_t handle) { if (handle == 0 || handle >= g_glRaytracingScene.meshHandleToIndex.size()) return nullptr; const int index = g_glRaytracingScene.meshHandleToIndex[handle]; if (index < 0 || (size_t)index >= g_glRaytracingScene.meshes.size()) return nullptr; glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[(size_t)index]; if (!mesh.alive || mesh.handle != handle) return nullptr; return &mesh; } static const glRaytracingMeshRecord_t* glRaytracingFindMeshConst(uint32_t handle) { if (handle == 0 || handle >= g_glRaytracingScene.meshHandleToIndex.size()) return nullptr; const int index = g_glRaytracingScene.meshHandleToIndex[handle]; if (index < 0 || (size_t)index >= g_glRaytracingScene.meshes.size()) return nullptr; const glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[(size_t)index]; if (!mesh.alive || mesh.handle != handle) return nullptr; return &mesh; } static glRaytracingInstanceRecord_t* glRaytracingFindInstance(glRaytracingRenderWorld_t* world, uint32_t handle) { if (!world || handle == 0 || handle >= world->instanceHandleToIndex.size()) return nullptr; const int index = world->instanceHandleToIndex[handle]; if (index < 0 || (size_t)index >= world->instances.size()) return nullptr; glRaytracingInstanceRecord_t& inst = world->instances[(size_t)index]; if (!inst.alive || inst.handle != handle) return nullptr; return &inst; } static const glRaytracingInstanceRecord_t* glRaytracingFindInstanceConst(const glRaytracingRenderWorld_t* world, uint32_t handle) { if (!world || handle == 0 || handle >= world->instanceHandleToIndex.size()) return nullptr; const int index = world->instanceHandleToIndex[handle]; if (index < 0 || (size_t)index >= world->instances.size()) return nullptr; const glRaytracingInstanceRecord_t& inst = world->instances[(size_t)index]; if (!inst.alive || inst.handle != handle) return nullptr; return &inst; } static int glRaytracingEnsureMeshScratch(glRaytracingMeshRecord_t* mesh, UINT64 requiredScratch) { if (!mesh) return 0; requiredScratch = glRaytracingAlignUp(requiredScratch, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT); if (!mesh->blasScratch.resource || mesh->blasScratchSize < requiredScratch) { mesh->blasScratch.resource.Reset(); mesh->blasScratch = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), requiredScratch, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); if (!mesh->blasScratch.resource) return 0; mesh->blasScratchSize = requiredScratch; } return 1; } static int glRaytracingEnsureMeshResultBuffers(glRaytracingMeshRecord_t* mesh, UINT64 requiredResult) { if (!mesh) return 0; requiredResult = glRaytracingAlignUp(requiredResult, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT); const int resultCount = mesh->descCpu.allowUpdate ? 2 : 1; for (int i = 0; i < resultCount; ++i) { if (!mesh->blasResult[i].resource || mesh->blasResultSize < requiredResult) { mesh->blasResult[i].resource.Reset(); mesh->blasResult[i] = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), requiredResult, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); if (!mesh->blasResult[i].resource) return 0; } } if (!mesh->descCpu.allowUpdate) mesh->blasResult[1].resource.Reset(); mesh->blasResultSize = requiredResult; return 1; } static inline void glRaytracingBuildInstanceDesc( D3D12_RAYTRACING_INSTANCE_DESC* outDesc, const glRaytracingInstanceRecord_t& inst, D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA) { memcpy(outDesc->Transform, inst.descCpu.transform, sizeof(float) * 12); outDesc->InstanceID = inst.descCpu.instanceID; outDesc->InstanceMask = (UINT8)(inst.descCpu.mask ? inst.descCpu.mask : 0xFF); outDesc->InstanceContributionToHitGroupIndex = 0; outDesc->Flags = D3D12_RAYTRACING_INSTANCE_FLAG_NONE; outDesc->AccelerationStructure = blasGpuVA; } static void glRaytracingInvalidateInstanceCache(glRaytracingInstanceRecord_t* inst) { if (!inst) return; inst->cachedActive = 0; inst->cachedBlasGpuVA = 0; memset(&inst->cachedDescCpu, 0, sizeof(inst->cachedDescCpu)); } static int glRaytracingResolveInstanceDesc( glRaytracingInstanceRecord_t* inst, D3D12_RAYTRACING_INSTANCE_DESC* outDesc, D3D12_GPU_VIRTUAL_ADDRESS* outBlasGpuVA) { if (!inst || !inst->alive) return 0; const glRaytracingMeshRecord_t* mesh = glRaytracingFindMeshConst(inst->descCpu.meshHandle); if (!mesh || !mesh->blasBuilt) return 0; const glRaytracingBuffer_t* blas = glRaytracingGetMeshCurrentBLASConst(mesh); if (!blas || !blas->resource || blas->gpuVA == 0) return 0; if (outDesc) glRaytracingBuildInstanceDesc(outDesc, *inst, blas->gpuVA); if (outBlasGpuVA) *outBlasGpuVA = blas->gpuVA; return 1; } static int glRaytracingRebuildActiveInstanceCache(glRaytracingRenderWorld_t* world) { if (!world) return 0; world->activeInstanceIndices.clear(); world->cpuInstanceDescs.clear(); world->activeInstanceIndices.reserve(world->instances.size()); world->cpuInstanceDescs.reserve(world->instances.size()); for (size_t i = 0; i < world->instances.size(); ++i) { glRaytracingInstanceRecord_t& inst = world->instances[i]; glRaytracingInvalidateInstanceCache(&inst); if (!inst.alive) continue; D3D12_RAYTRACING_INSTANCE_DESC desc = {}; D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA = 0; if (!glRaytracingResolveInstanceDesc(&inst, &desc, &blasGpuVA)) continue; inst.cachedActive = 1; inst.cachedBlasGpuVA = blasGpuVA; inst.cachedDescCpu = desc; inst.dirty = 0; world->activeInstanceIndices.push_back((int)i); world->cpuInstanceDescs.push_back(desc); } world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size(); return 1; } static int glRaytracingRefreshDirtyInstanceCache(glRaytracingRenderWorld_t* world) { if (!world) return 0; for (size_t listIndex = 0; listIndex < world->activeInstanceIndices.size(); ++listIndex) { const int instIndex = world->activeInstanceIndices[listIndex]; if (instIndex < 0 || (size_t)instIndex >= world->instances.size()) return 0; glRaytracingInstanceRecord_t& inst = world->instances[(size_t)instIndex]; if (!inst.alive) return 0; D3D12_RAYTRACING_INSTANCE_DESC desc = {}; D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA = 0; if (!glRaytracingResolveInstanceDesc(&inst, &desc, &blasGpuVA)) return 0; if (inst.dirty || !inst.cachedActive || inst.cachedBlasGpuVA != blasGpuVA) { inst.cachedActive = 1; inst.cachedBlasGpuVA = blasGpuVA; inst.cachedDescCpu = desc; world->cpuInstanceDescs[listIndex] = desc; } inst.dirty = 0; } world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size(); return 1; } static int glRaytracingEnsureSceneUploadBuffer(glRaytracingRenderWorld_t* world, UINT64 requiredBytes); static int glRaytracingUploadCachedInstanceDescs(glRaytracingRenderWorld_t* world) { if (!world) return 0; const UINT activeCount = (UINT)world->cpuInstanceDescs.size(); const UINT64 instBytes = glRaytracingAlignUp( (UINT64)activeCount * (UINT64)sizeof(D3D12_RAYTRACING_INSTANCE_DESC), D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT); if (!glRaytracingEnsureSceneUploadBuffer(world, instBytes)) return 0; glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world); if (!upload->mapped) return 0; if (activeCount > 0) memcpy(upload->mapped, world->cpuInstanceDescs.data(), (size_t)activeCount * sizeof(D3D12_RAYTRACING_INSTANCE_DESC)); return 1; } static int glRaytracingUploadMeshBuffers(glRaytracingMeshRecord_t* mesh); static int glRaytracingBuildDirtyMeshesInternal(void) { std::vector dirtyMeshes; dirtyMeshes.reserve(g_glRaytracingScene.meshes.size()); for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i) { glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i]; if (!mesh.alive) continue; if (!mesh.blasBuilt || mesh.dirty) dirtyMeshes.push_back(&mesh); } if (dirtyMeshes.empty()) return 1; struct glRaytracingMeshBuildInfo_t { glRaytracingMeshRecord_t* mesh; D3D12_RAYTRACING_GEOMETRY_DESC geomDesc; D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs; D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc; ID3D12Resource* barrierResource; int newBlasIndex; }; std::vector builds; builds.resize(dirtyMeshes.size()); for (size_t i = 0; i < dirtyMeshes.size(); ++i) { glRaytracingMeshRecord_t* mesh = dirtyMeshes[i]; if (!mesh->vertexBuffer.resource || !mesh->indexBuffer.resource) { if (!glRaytracingUploadMeshBuffers(mesh)) return 0; } glRaytracingMeshBuildInfo_t& info = builds[i]; memset(&info, 0, sizeof(info)); info.mesh = mesh; info.geomDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; info.geomDesc.Flags = mesh->descCpu.opaque ? D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE : D3D12_RAYTRACING_GEOMETRY_FLAG_NONE; info.geomDesc.Triangles.Transform3x4 = 0; info.geomDesc.Triangles.IndexFormat = DXGI_FORMAT_R32_UINT; info.geomDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; info.geomDesc.Triangles.IndexCount = (UINT)mesh->indicesCpu.size(); info.geomDesc.Triangles.VertexCount = (UINT)mesh->verticesCpu.size(); info.geomDesc.Triangles.IndexBuffer = mesh->indexBuffer.gpuVA; info.geomDesc.Triangles.VertexBuffer.StartAddress = mesh->vertexBuffer.gpuVA; info.geomDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(glRaytracingVertex_t); info.inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; info.inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; info.inputs.NumDescs = 1; info.inputs.pGeometryDescs = &info.geomDesc; info.inputs.Flags = mesh->descCpu.allowUpdate ? (D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE | D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE) : D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; const bool canUpdateInPlace = (mesh->blasBuilt != 0) && (mesh->descCpu.allowUpdate != 0); if (canUpdateInPlace) info.inputs.Flags |= D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PERFORM_UPDATE; D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuild = {}; g_glRaytracingCmd.device->GetRaytracingAccelerationStructurePrebuildInfo(&info.inputs, &prebuild); if (prebuild.ResultDataMaxSizeInBytes == 0) { glRaytracingFatal("BLAS prebuild size is zero"); return 0; } if (!glRaytracingEnsureMeshScratch(mesh, prebuild.ScratchDataSizeInBytes)) return 0; if (!glRaytracingEnsureMeshResultBuffers(mesh, prebuild.ResultDataMaxSizeInBytes)) return 0; const int oldIndex = mesh->currentBlasIndex & 1; info.newBlasIndex = (mesh->descCpu.allowUpdate && mesh->blasBuilt) ? (oldIndex ^ 1) : oldIndex; info.buildDesc.Inputs = info.inputs; info.buildDesc.ScratchAccelerationStructureData = mesh->blasScratch.gpuVA; info.buildDesc.DestAccelerationStructureData = mesh->blasResult[info.newBlasIndex].gpuVA; info.buildDesc.SourceAccelerationStructureData = 0; if (canUpdateInPlace) info.buildDesc.SourceAccelerationStructureData = mesh->blasResult[oldIndex].gpuVA; info.barrierResource = mesh->blasResult[info.newBlasIndex].resource.Get(); } if (!glRaytracingBeginBlasCmd()) return 0; for (size_t i = 0; i < builds.size(); ++i) { g_glRaytracingCmd.blasCmdList->BuildRaytracingAccelerationStructure(&builds[i].buildDesc, 0, nullptr); D3D12_RESOURCE_BARRIER uav = {}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = builds[i].barrierResource; g_glRaytracingCmd.blasCmdList->ResourceBarrier(1, &uav); } const UINT64 blasFenceValue = glRaytracingEndBlasCmd(); if (!blasFenceValue) return 0; glRaytracingWaitFenceValue(blasFenceValue); for (size_t i = 0; i < builds.size(); ++i) { glRaytracingMeshRecord_t* mesh = builds[i].mesh; mesh->currentBlasIndex = builds[i].newBlasIndex; mesh->blasBuilt = 1; mesh->dirty = 0; } glRaytracingMarkAllWorldsNeedRebuild(); return 1; } static int glRaytracingUploadMeshBuffers(glRaytracingMeshRecord_t* mesh) { if (!mesh) return 0; if (mesh->verticesCpu.empty() || mesh->indicesCpu.empty()) return 0; const UINT64 vbBytes = UINT64(mesh->verticesCpu.size()) * sizeof(glRaytracingVertex_t); const UINT64 ibBytes = UINT64(mesh->indicesCpu.size()) * sizeof(uint32_t); mesh->vertexBuffer = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), vbBytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); mesh->indexBuffer = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), ibBytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); if (!mesh->vertexBuffer.resource || !mesh->indexBuffer.resource) return 0; glRaytracingMapCopy(mesh->vertexBuffer.resource.Get(), mesh->verticesCpu.data(), (size_t)vbBytes); glRaytracingMapCopy(mesh->indexBuffer.resource.Get(), mesh->indicesCpu.data(), (size_t)ibBytes); return 1; } static int glRaytracingBuildMeshInternal(glRaytracingMeshRecord_t* mesh) { if (!mesh) return 0; const int oldDirty = mesh->dirty; mesh->dirty = 1; const int ok = glRaytracingBuildDirtyMeshesInternal(); if (!ok) mesh->dirty = oldDirty; return ok; } static int glRaytracingEnsureSceneUploadBuffer(glRaytracingRenderWorld_t* world, UINT64 requiredBytes) { if (!world) return 0; glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world); if (requiredBytes == 0) requiredBytes = D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT; requiredBytes = glRaytracingAlignUp( requiredBytes, D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT); if (upload->buffer.resource && upload->capacityBytes >= requiredBytes && upload->mapped) { return 1; } if (upload->buffer.resource && upload->mapped) upload->buffer.resource->Unmap(0, nullptr); upload->mapped = nullptr; upload->buffer.resource.Reset(); upload->capacityBytes = 0; upload->buffer = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), requiredBytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); if (!upload->buffer.resource) return 0; void* mapped = nullptr; D3D12_RANGE readRange = {}; if (FAILED(upload->buffer.resource->Map(0, &readRange, &mapped)) || !mapped) { upload->buffer.resource.Reset(); return 0; } upload->mapped = (D3D12_RAYTRACING_INSTANCE_DESC*)mapped; upload->capacityBytes = requiredBytes; return 1; } static int glRaytracingBuildSceneInternal(glRaytracingRenderWorld_t* world) { if (!world || !world->alive) return 0; UINT aliveCount = 0; int anyDirty = 0; int needsRebuild = world->tlasNeedsRebuild; int needsUpdate = world->tlasNeedsUpdate; for (size_t i = 0; i < world->instances.size(); ++i) { const glRaytracingInstanceRecord_t& inst = world->instances[i]; if (!inst.alive) continue; ++aliveCount; if (inst.dirty) anyDirty = 1; } if (aliveCount == 0) { world->activeInstanceIndices.clear(); world->cpuInstanceDescs.clear(); world->activeInstanceCount = 0; world->builtInstanceCount = 0; world->tlasBuilt = 0; world->tlasNeedsRebuild = 0; world->tlasNeedsUpdate = 0; return 1; } if (!world->tlasBuilt) needsRebuild = 1; if ((UINT)world->activeInstanceIndices.size() != world->builtInstanceCount) needsRebuild = 1; if (needsRebuild) { if (!glRaytracingRebuildActiveInstanceCache(world)) return 0; } else { if (!needsUpdate && !anyDirty) { world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size(); return 1; } if (!glRaytracingRefreshDirtyInstanceCache(world)) { world->tlasNeedsRebuild = 1; if (!glRaytracingRebuildActiveInstanceCache(world)) return 0; needsRebuild = 1; } } const UINT activeCount = (UINT)world->cpuInstanceDescs.size(); if (activeCount == 0) { world->activeInstanceCount = 0; world->builtInstanceCount = 0; world->tlasBuilt = 0; world->tlasNeedsRebuild = 0; world->tlasNeedsUpdate = 0; return 1; } if (!world->tlasBuilt || activeCount != world->builtInstanceCount) needsRebuild = 1; if (!glRaytracingUploadCachedInstanceDescs(world)) return 0; glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world); D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs = {}; inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; inputs.NumDescs = activeCount; inputs.InstanceDescs = upload->buffer.gpuVA; inputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE | D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE; if (!glRaytracingEnsureTLASBuffers(world, &inputs)) return 0; glRaytracingBuffer_t* dstTLAS = glRaytracingGetBuildTLASBuffer(world); const glRaytracingBuffer_t* srcTLAS = glRaytracingGetCurrentTLASBufferConst(world); glRaytracingWaitFenceValue(g_glRaytracingCmd.blasLastFenceValue); if (!glRaytracingBeginTlasCmd()) return 0; D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc = {}; buildDesc.Inputs = inputs; buildDesc.ScratchAccelerationStructureData = world->tlasScratch.gpuVA; buildDesc.DestAccelerationStructureData = dstTLAS->gpuVA; buildDesc.SourceAccelerationStructureData = 0; if (!needsRebuild && world->tlasBuilt) { buildDesc.Inputs.Flags |= D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PERFORM_UPDATE; buildDesc.SourceAccelerationStructureData = srcTLAS->gpuVA; } g_glRaytracingCmd.tlasCmdList->BuildRaytracingAccelerationStructure(&buildDesc, 0, nullptr); D3D12_RESOURCE_BARRIER uav = {}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = dstTLAS->resource.Get(); g_glRaytracingCmd.tlasCmdList->ResourceBarrier(1, &uav); const UINT64 tlasFenceValue = glRaytracingEndTlasCmd(); if (!tlasFenceValue) return 0; glRaytracingWaitFenceValue(tlasFenceValue); world->currentTLASIndex = glRaytracingGetInactiveTLASIndex(world); world->activeInstanceCount = activeCount; world->builtInstanceCount = activeCount; world->tlasBuilt = 1; world->tlasNeedsRebuild = 0; world->tlasNeedsUpdate = 0; for (size_t i = 0; i < world->instances.size(); ++i) { if (world->instances[i].alive) world->instances[i].dirty = 0; } return 1; } static void glRaytracingInvalidateInstancesForMesh(uint32_t meshHandle, int deleteInstances) { for (int w = 0; w < GL_RAYTRACING_MAX_RENDER_WORLDS; ++w) { glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[w]; if (!world.alive) continue; int touched = 0; for (size_t i = 0; i < world.instances.size(); ++i) { glRaytracingInstanceRecord_t& inst = world.instances[i]; if (inst.alive && inst.descCpu.meshHandle == meshHandle) { glRaytracingInvalidateInstanceCache(&inst); inst.dirty = 1; touched = 1; if (deleteInstances) { inst.alive = 0; if (inst.handle < world.instanceHandleToIndex.size()) world.instanceHandleToIndex[inst.handle] = -1; } } } if (touched) glRaytracingMarkWorldNeedsRebuild(&world); } } // ============================================================ // Scene public API // ============================================================ int glRaytracingInit(void) { std::lock_guard lock(g_glRaytracingMutex); if (g_glRaytracingScene.initialized) return 1; if (!glRaytracingInitCmdContext()) return 0; g_glRaytracingScene.initialized = 1; glRaytracingLog("glRaytracingInit ok"); return 1; } void glRaytracingShutdown(void) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return; glRaytracingClearAllSceneStateInternal(); g_glRaytracingScene = glRaytracingSceneState_t(); glRaytracingShutdownCmdContext(); } void glRaytracingClear(void) { std::lock_guard lock(g_glRaytracingMutex); glRaytracingClearAllSceneStateInternal(); } glRaytracingSceneHandle_t glRaytracingCreateScene(void) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return 0; for (uint32_t i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i) { glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[i]; if (!world.alive) { const uint32_t handle = i + 1; glRaytracingResetWorldSlot(&world, handle, 1); return handle; } } return 0; } void glRaytracingClearScene(glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return; glRaytracingClearWorldContents(world); } void glRaytracingDeleteScene(glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return; glRaytracingReleaseWorldResources(world); *world = glRaytracingRenderWorld_t(); } uint32_t glRaytracingGetSceneCount(void) { std::lock_guard lock(g_glRaytracingMutex); uint32_t count = 0; for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i) { if (g_glRaytracingScene.worlds[i].alive) ++count; } return count; } glRaytracingMeshHandle_t glRaytracingCreateMesh(const glRaytracingMeshDesc_t* desc) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized || !desc) return 0; if (!desc->vertices || !desc->indices || desc->vertexCount == 0 || desc->indexCount == 0) return 0; glRaytracingMeshRecord_t mesh; mesh.handle = g_glRaytracingScene.nextMeshHandle++; mesh.alive = 1; mesh.descCpu = *desc; mesh.verticesCpu.assign(desc->vertices, desc->vertices + desc->vertexCount); mesh.indicesCpu.assign(desc->indices, desc->indices + desc->indexCount); mesh.descCpu.vertices = nullptr; mesh.descCpu.indices = nullptr; mesh.dirty = 1; g_glRaytracingScene.meshes.push_back(mesh); const size_t newIndex = g_glRaytracingScene.meshes.size() - 1; glRaytracingEnsureMeshHandleTable(mesh.handle); g_glRaytracingScene.meshHandleToIndex[mesh.handle] = (int)newIndex; return mesh.handle; } int glRaytracingUpdateMesh(glRaytracingMeshHandle_t meshHandle, const glRaytracingMeshDesc_t* desc) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized || !desc) return 0; glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle); if (!mesh) return 0; if (!desc->vertices || !desc->indices || desc->vertexCount == 0 || desc->indexCount == 0) return 0; mesh->descCpu = *desc; mesh->verticesCpu.assign(desc->vertices, desc->vertices + desc->vertexCount); mesh->indicesCpu.assign(desc->indices, desc->indices + desc->indexCount); mesh->descCpu.vertices = nullptr; mesh->descCpu.indices = nullptr; mesh->vertexBuffer.resource.Reset(); mesh->indexBuffer.resource.Reset(); mesh->blasScratch.resource.Reset(); mesh->blasResult[0].resource.Reset(); mesh->blasResult[1].resource.Reset(); mesh->blasScratchSize = 0; mesh->blasResultSize = 0; mesh->blasBuilt = 0; mesh->dirty = 1; mesh->currentBlasIndex = 0; glRaytracingInvalidateInstancesForMesh(meshHandle, 0); glRaytracingMarkAllWorldsNeedRebuild(); return 1; } void glRaytracingDeleteMesh(glRaytracingMeshHandle_t meshHandle) { std::lock_guard lock(g_glRaytracingMutex); glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle); if (!mesh) return; glRaytracingInvalidateInstancesForMesh(meshHandle, 1); mesh->alive = 0; mesh->vertexBuffer.resource.Reset(); mesh->indexBuffer.resource.Reset(); mesh->blasScratch.resource.Reset(); mesh->blasResult[0].resource.Reset(); mesh->blasResult[1].resource.Reset(); mesh->blasScratchSize = 0; mesh->blasResultSize = 0; mesh->blasBuilt = 0; mesh->dirty = 0; if (meshHandle < g_glRaytracingScene.meshHandleToIndex.size()) g_glRaytracingScene.meshHandleToIndex[meshHandle] = -1; glRaytracingMarkAllWorldsNeedRebuild(); } glRaytracingInstanceHandle_t glRaytracingCreateInstanceInScene(glRaytracingSceneHandle_t worldHandle, const glRaytracingInstanceDesc_t* desc) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized || !desc) return 0; glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return 0; if (!glRaytracingFindMeshConst(desc->meshHandle)) return 0; glRaytracingInstanceRecord_t inst; inst.handle = world->nextInstanceHandle++; inst.alive = 1; inst.descCpu = *desc; inst.dirty = 1; world->instances.push_back(inst); const size_t newIndex = world->instances.size() - 1; glRaytracingEnsureInstanceHandleTable(world, inst.handle); world->instanceHandleToIndex[inst.handle] = (int)newIndex; glRaytracingMarkWorldNeedsRebuild(world); return inst.handle; } int glRaytracingUpdateInstanceInScene(glRaytracingSceneHandle_t worldHandle, glRaytracingInstanceHandle_t instanceHandle, const glRaytracingInstanceDesc_t* desc) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized || !desc) return 0; glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return 0; if (!glRaytracingFindMeshConst(desc->meshHandle)) return 0; glRaytracingInstanceRecord_t* inst = glRaytracingFindInstance(world, instanceHandle); if (!inst) return 0; const uint32_t oldMeshHandle = inst->descCpu.meshHandle; inst->descCpu = *desc; inst->dirty = 1; if (oldMeshHandle != desc->meshHandle) { glRaytracingInvalidateInstanceCache(inst); glRaytracingMarkWorldNeedsRebuild(world); } else { glRaytracingMarkWorldNeedsUpdate(world); } return 1; } void glRaytracingDeleteInstanceInScene(glRaytracingSceneHandle_t worldHandle, glRaytracingInstanceHandle_t instanceHandle) { std::lock_guard lock(g_glRaytracingMutex); glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return; glRaytracingInstanceRecord_t* inst = glRaytracingFindInstance(world, instanceHandle); if (!inst) return; glRaytracingInvalidateInstanceCache(inst); inst->alive = 0; if (instanceHandle < world->instanceHandleToIndex.size()) world->instanceHandleToIndex[instanceHandle] = -1; glRaytracingMarkWorldNeedsRebuild(world); } int glRaytracingBuildMesh(glRaytracingMeshHandle_t meshHandle) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return 0; glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle); if (!mesh) return 0; return glRaytracingBuildMeshInternal(mesh); } int glRaytracingBuildAllMeshes(void) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return 0; return glRaytracingBuildDirtyMeshesInternal(); } int glRaytracingBuildSceneForHandle(glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return 0; glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return 0; if (!glRaytracingBuildDirtyMeshesInternal()) return 0; return glRaytracingBuildSceneInternal(world); } ID3D12Resource* glRaytracingGetTopLevelASForScene(glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingScene.initialized) return nullptr; glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return nullptr; if (!glRaytracingBuildDirtyMeshesInternal()) return nullptr; if (!glRaytracingBuildSceneInternal(world)) return nullptr; if (!world->tlasBuilt) return nullptr; return glRaytracingGetCurrentTLASBuffer(world)->resource.Get(); } uint32_t glRaytracingGetMeshCount(void) { std::lock_guard lock(g_glRaytracingMutex); uint32_t count = 0; for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i) { if (g_glRaytracingScene.meshes[i].alive) ++count; } return count; } uint32_t glRaytracingGetInstanceCountForScene(glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); const glRaytracingRenderWorld_t* world = glRaytracingFindWorldConst(worldHandle); return glRaytracingCountAliveInstances(world); } // ============================================================ // Lighting state // ============================================================ struct glRaytracingLightingConstants_t { float invViewProj[16]; float invViewMatrix[16]; float cameraPos[4]; float ambientColor[4]; float screenSize[4]; float normalReconstructZ; uint32_t lightCount; uint32_t enableSpecular; uint32_t enableHalfLambert; float shadowBias; }; struct glRaytracingLightingState_t { std::vector cpuLights; glRaytracingLightingConstants_t constants; ComPtr descriptorHeap; UINT descriptorStride; glRaytracingBuffer_t constantBuffer; glRaytracingBuffer_t lightBuffer; ComPtr globalRootSig; ComPtr localRootSig; ComPtr rtStateObject; ComPtr rtStateProps; glRaytracingBuffer_t raygenTable; glRaytracingBuffer_t missTable; glRaytracingBuffer_t hitTable; bool initialized; glRaytracingLightingState_t() { memset(&constants, 0, sizeof(constants)); descriptorStride = 0; initialized = false; } }; static glRaytracingLightingState_t g_glRaytracingLighting; static const char* g_glRaytracingLightingHlsl = R"( struct Light { float3 position; float radius; float3 color; float intensity; float3 normal; uint type; float3 axisU; float halfWidth; float3 axisV; float halfHeight; uint samples; uint twoSided; float persistant; float pad1; // For point lights, this is the axis-aligned XYZ attenuation radius. // For spot lights, pointRadius.x stores the near clip plane. // The scalar radius above is still kept as a max/fallback range for point lights, // as the influence range for rect lights, and as the far clip distance for spot lights. float3 pointRadius; float pointRadiusPad; // non-zero disables specular for this light }; struct ShadowPayload { uint hit; }; cbuffer LightingCB : register(b0) { float4x4 gInvViewProj; float4x4 gInvViewMatrix; float4 gCameraPos; float4 gAmbientColor; float4 gScreenSize; float gNormalReconstructZ; uint gLightCount; uint gEnableSpecular; uint gEnableHalfLambert; float gShadowBias; }; StructuredBuffer gLights : register(t0); Texture2D gAlbedoTex : register(t1); Texture2D gDepthTex : register(t2); Texture2D gNormalTex : register(t3); Texture2D gPositionTex : register(t4); RaytracingAccelerationStructure gSceneBVH : register(t5); RWTexture2D gOutputTex : register(u0); static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0; static const uint GL_RAYTRACING_LIGHT_TYPE_RECT = 1; static const uint GL_RAYTRACING_LIGHT_TYPE_SPOT = 2; static const uint GEOMETRY_FLAG_SKELETAL = 1; static const uint GEOMETRY_FLAG_UNLIT = 2; float3 LoadScenePosition(uint2 pixel) { float4 p = gPositionTex.Load(int3(pixel, 0)); return p.xyz; } float4 LoadSceneNormal(uint2 pixel) { float4 nSample = gNormalTex.Load(int3(pixel, 0)); return nSample; } [shader("miss")] void ShadowMiss(inout ShadowPayload payload) { payload.hit = 0; } [shader("closesthit")] void ShadowClosestHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr) { payload.hit = 1; } float TraceShadow(float3 origin, float3 dir, float maxT) { RayDesc ray; ray.Origin = origin; ray.Direction = dir; ray.TMin = 0.001; ray.TMax = maxT; ShadowPayload payload; payload.hit = 0; TraceRay( gSceneBVH, RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES, 0xFF, 0, 1, 0, ray, payload); return (payload.hit != 0) ? 0.0 : 1.0; } float Hash12(float2 p) { float3 p3 = frac(float3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return frac((p3.x + p3.y) * p3.z); } float2 Hammersley2D(uint i, uint N, float rand) { float e1 = frac((float)i / (float)N + rand); uint bits = i; bits = (bits << 16) | (bits >> 16); bits = ((bits & 0x55555555u) << 1) | ((bits & 0xAAAAAAAAu) >> 1); bits = ((bits & 0x33333333u) << 2) | ((bits & 0xCCCCCCCCu) >> 2); bits = ((bits & 0x0F0F0F0Fu) << 4) | ((bits & 0xF0F0F0F0u) >> 4); bits = ((bits & 0x00FF00FFu) << 8) | ((bits & 0xFF00FF00u) >> 8); float e2 = (float)bits * 2.3283064365386963e-10; return float2(e1, e2); } float2 ConcentricSampleDisk(float2 u) { float2 uOffset = 2.0 * u - 1.0; if (uOffset.x == 0.0 && uOffset.y == 0.0) return float2(0.0, 0.0); float r, theta; if (abs(uOffset.x) > abs(uOffset.y)) { r = uOffset.x; theta = (3.14159265 / 4.0) * (uOffset.y / uOffset.x); } else { r = uOffset.y; theta = (3.14159265 / 2.0) - (3.14159265 / 4.0) * (uOffset.x / uOffset.y); } return r * float2(cos(theta), sin(theta)); } void BuildOrthonormalBasis(float3 n, out float3 t, out float3 b) { float3 up = (abs(n.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0); t = normalize(cross(up, n)); b = cross(n, t); } float3 CosineSampleHemisphere(float2 u) { float2 d = ConcentricSampleDisk(u); float z = sqrt(saturate(1.0 - dot(d, d))); return float3(d.x, d.y, z); } float3 GetPointLightRadius(Light Lgt) { float scalarRadius = max(abs(Lgt.radius), 1e-4); float3 r = abs(Lgt.pointRadius); // Allow older/zero-initialized light records to behave like the old scalar radius. if (max(max(r.x, r.y), r.z) <= 1e-4) { r = float3(scalarRadius, scalarRadius, scalarRadius); } return max(r, float3(1e-4, 1e-4, 1e-4)); } float GetPointLightMaxRadius(Light Lgt) { float3 r = GetPointLightRadius(Lgt); return max(max(r.x, r.y), r.z); } float ComputePointLightAttenuation(float3 worldPos, Light Lgt) { float3 radii = GetPointLightRadius(Lgt); float3 normalizedOffset = (worldPos - Lgt.position) / radii; // Ellipsoidal falloff: radius.x controls X reach, radius.y controls Y reach, // and radius.z controls Z reach in world space. float ellipsoidDistance = length(normalizedOffset); float atten = saturate(1.0 - ellipsoidDistance); return atten; } float ComputeSpotLightAttenuation(float3 worldPos, Light Lgt) { float3 lightToSurface = worldPos - Lgt.position; float nearClip = max(Lgt.pointRadius.x, 0.0); float farClip = max(Lgt.radius, nearClip + 1e-4); float depth = dot(lightToSurface, Lgt.normal); if (depth <= nearClip || depth >= farClip) return 0.0; float invDepth = 1.0 / max(depth, 1e-4); float projU = dot(lightToSurface, Lgt.axisU) * invDepth; float projV = dot(lightToSurface, Lgt.axisV) * invDepth; float halfU = max(abs(Lgt.halfWidth), 1e-4); float halfV = max(abs(Lgt.halfHeight), 1e-4); float edgeU = abs(projU) / halfU; float edgeV = abs(projV) / halfV; float edge = max(edgeU, edgeV); if (edge >= 1.0) return 0.0; float coneAtten = saturate(1.0 - edge); coneAtten = coneAtten; float rangeAtten = saturate((farClip - depth) / max(farClip - nearClip, 1e-4)); rangeAtten = rangeAtten; return coneAtten * rangeAtten; } float TraceSpotShadow(float3 worldPos, float3 N, float3 toLight, float dist) { float3 L = toLight / max(dist, 1e-6); float NdotLRaw = saturate(dot(N, L)); float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NdotLRaw); float3 shadowOrigin = worldPos + N * normalBias + L * (gShadowBias * 0.5); float shadowTMax = max(dist - gShadowBias * 0.5, 0.001); return TraceShadow(shadowOrigin, L, shadowTMax); } float TraceSoftShadow(float3 worldPos, float3 N, Light Lgt, float3 toLight, float dist) { const uint SHADOW_SAMPLES = 12; float3 L = toLight / max(dist, 1e-6); float3 tangent, bitangent; BuildOrthonormalBasis(L, tangent, bitangent); float areaRadius = max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12); float shadowAccum = 0.0; float rand = Hash12(worldPos.xy + float2(worldPos.z, dist)); [unroll] for (uint s = 0; s < SHADOW_SAMPLES; ++s) { float2 xi = Hammersley2D(s, SHADOW_SAMPLES, rand); float2 d = ConcentricSampleDisk(xi) * areaRadius; float3 sampleLightPos = Lgt.position + tangent * d.x + bitangent * d.y; float3 sampleVec = sampleLightPos - worldPos; float sampleDist = length(sampleVec); if (sampleDist <= 1e-4) { shadowAccum += 1.0; continue; } float3 sampleDir = sampleVec / sampleDist; float NdotLRaw = saturate(dot(N, sampleDir)); float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NdotLRaw); float3 shadowOrigin = worldPos + N * normalBias + sampleDir * (gShadowBias * 0.5); float shadowTMax = max(sampleDist - gShadowBias * 0.5, 0.001); shadowAccum += TraceShadow(shadowOrigin, sampleDir, shadowTMax); } return shadowAccum / (float)SHADOW_SAMPLES; } float RectLightShadow(float3 worldPos, float3 N, Light Lgt, uint2 pixel) { uint sampleCount = max(Lgt.samples, 1u); sampleCount = min(sampleCount, 16u); float visibility = 0.0; float rand = Hash12((float2)pixel + worldPos.xy + float2(worldPos.z, dot(N.xy, N.xy))); float NoL_center = saturate(dot(N, normalize(Lgt.position - worldPos))); float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 0.75, NoL_center); float3 baseOrigin = worldPos + N * normalBias; [loop] for (uint s = 0; s < sampleCount; ++s) { float2 xi = Hammersley2D(s, sampleCount, rand); float2 uv = xi * 2.0 - 1.0; float3 sampleLightPos = Lgt.position + Lgt.axisU * (uv.x * Lgt.halfWidth) + Lgt.axisV * (uv.y * Lgt.halfHeight); float3 toLight = sampleLightPos - baseOrigin; float distToLight = length(toLight); if (distToLight <= 1e-4) { visibility += 1.0; continue; } float3 L = toLight / distToLight; float NdotL = dot(N, L); if (NdotL <= 0.0) { continue; } float emitTerm = (Lgt.twoSided != 0) ? abs(dot(Lgt.normal, -L)) : dot(Lgt.normal, -L); if (emitTerm <= 0.0) { continue; } float3 shadowOrigin = baseOrigin + L * (gShadowBias * 0.5); float shadowTMax = max(distToLight - gShadowBias, 0.001); visibility += TraceShadow(shadowOrigin, L, shadowTMax); } return visibility / (float)sampleCount; } float ComputeAmbientOcclusion(float3 worldPos, float3 N, uint2 pixel) { const uint AO_SAMPLES = 24; const float AO_RADIUS = 32.0; float3 tangent, bitangent; BuildOrthonormalBasis(N, tangent, bitangent); float rand = Hash12((float2)pixel + worldPos.xy + worldPos.zz); float visibility = 0.0; [unroll] for (uint i = 0; i < AO_SAMPLES; ++i) { float2 xi = Hammersley2D(i, AO_SAMPLES, rand); float3 h = CosineSampleHemisphere(xi); float3 aoDir = tangent * h.x + bitangent * h.y + N * h.z; aoDir = normalize(aoDir); float3 aoOrigin = worldPos + N * (gShadowBias * 0.15); visibility += TraceShadow(aoOrigin, aoDir, AO_RADIUS); } visibility /= (float)AO_SAMPLES; visibility = saturate(pow(visibility, 1.5)); return visibility; } float ComputeSkyVisibility(float3 worldPos, float3 N, uint2 pixel) { const uint SKY_SAMPLES = 8; const float SKY_TMAX = 1000000.0; float3 tangent, bitangent; BuildOrthonormalBasis(N, tangent, bitangent); float rand = Hash12((float2)pixel * 1.37 + worldPos.xy + float2(worldPos.z, dot(N.xy, N.xy))); float vis = 0.0; [unroll] for (uint i = 0; i < SKY_SAMPLES; ++i) { float2 xi = Hammersley2D(i, SKY_SAMPLES, rand); float3 h = CosineSampleHemisphere(xi); float3 skyDir = tangent * h.x + bitangent * h.y + N * h.z; skyDir = normalize(skyDir); if (skyDir.z <= 0.05) continue; float3 skyOrigin = worldPos + N * (gShadowBias * 2.0) + skyDir * (gShadowBias * 2.0); vis += TraceShadow(skyOrigin, skyDir, SKY_TMAX); } vis /= (float)SKY_SAMPLES; return saturate(vis); } float ComputeCavity(uint2 pixel, float3 worldPos, float3 N) { static const int2 taps[12] = { int2(-2, 0), int2( 2, 0), int2( 0, -2), int2( 0, 2), int2(-2, -2), int2( 2, -2), int2(-2, 2), int2( 2, 2), int2(-4, 0), int2( 4, 0), int2( 0, -4), int2( 0, 4) }; float accum = 0.0; float weightSum = 0.0; [unroll] for (int i = 0; i < 12; ++i) { int2 sp = int2(pixel) + taps[i]; if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y) continue; float3 samplePos = gPositionTex.Load(int3(sp, 0)).xyz; float3 sampleN = normalize(gNormalTex.Load(int3(sp, 0)).xyz); float3 d = samplePos - worldPos; float distSq = dot(d, d); if (distSq > (24.0 * 24.0)) continue; float nd = dot(N, sampleN); if (nd < 0.65) continue; float curvature = 1.0 - saturate(nd); float w = 1.0 / (1.0 + distSq * 0.02); accum += curvature * w; weightSum += w; } float cavity = (weightSum > 0.0) ? (accum / weightSum) : 0.0; cavity = saturate(cavity * 2.0); return 1.0 - cavity * 0.18; } float3 ComputeSpecular(float3 N, float3 V, float3 L, float3 lightColor, float lightIntensity, float atten, float shadow, float3 baseAlbedo) { if (gEnableSpecular == 0) return 0.0; N = normalize(N); V = normalize(V); L = normalize(L); float NdotL = saturate(dot(N, L)); float NdotV = saturate(dot(N, V)); if (NdotL <= 0.0 || NdotV <= 0.0 || atten <= 0.0 || shadow <= 0.0) return 0.0; // Doom 3 / idTech4-style legacy specular: // - Phong reflection vector, not Blinn half-vector. // - Low exponent for broad plastic/metal highlights. // - Strong additive multiplier like the old interaction shader. // - Spec map behavior is approximated here by squaring baseAlbedo because this pass // currently has no dedicated specular texture bound. const float DOOM3_SPECULAR_POWER = 8.0; const float DOOM3_SPECULAR_SCALE = 6.0; float3 R = normalize(reflect(-L, N)); float RdotV = saturate(dot(R, V)); float specTerm = pow(RdotV, DOOM3_SPECULAR_POWER); // Doom 3 squared the specular map before applying it. Since this shader only has // baseAlbedo available, use squared albedo as a pseudo specular mask. float3 specMask = saturate(baseAlbedo * baseAlbedo); // Keep a small neutral floor so very dark diffuse textures can still catch a Doom 3 // style highlight when no real specular map exists. specMask = max(specMask, float3(0.08, 0.08, 0.08)); float3 spec = specMask * specTerm * DOOM3_SPECULAR_SCALE; return lightColor * (lightIntensity * atten * shadow * NdotL) * spec; } )" R"( [shader("raygeneration")] void RayGen() { uint2 pixel = DispatchRaysIndex().xy; if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y) return; float4 albedoSample = gAlbedoTex.Load(int3(pixel, 0)); float depthSample = gDepthTex.Load(int3(pixel, 0)); if (depthSample <= 0.0 || depthSample >= 1.0) { gOutputTex[pixel] = albedoSample; return; } float3 baseAlbedo = albedoSample.rgb; float4 positionSample = gPositionTex.Load(int3(pixel, 0)); float3 worldPos = positionSample.xyz; float4 normalSample = LoadSceneNormal(pixel); float3 N = normalize(normalSample.xyz); float3 V = normalize(gCameraPos.xyz - worldPos); float geoFlag = positionSample.w; float cavity = ComputeCavity(pixel, worldPos, N); float microShadow = lerp(0.75, 1.0, cavity); float3 albedo = baseAlbedo * cavity; albedo *= microShadow; float aoRay = ComputeAmbientOcclusion(worldPos, N, pixel); float ao = aoRay; float skyVis = ComputeSkyVisibility(worldPos, N, pixel); float upness = saturate(N.z * 0.5 + 0.5); float3 skyColor = float3(0.5, 0.5, 0.5) * (0.35 + 0.65 * upness); float skyStrength = 2.0; float3 lightingAccum = 0.0; float3 specularAccum = 0.0; lightingAccum += skyColor * (skyStrength * skyVis); if (geoFlag == GEOMETRY_FLAG_SKELETAL) { lightingAccum += 0.1; } [loop] for (uint i = 0; i < gLightCount; ++i) { Light Lgt = gLights[i]; if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) { float3 toLight = Lgt.position - worldPos; float distSq = dot(toLight, toLight); float dist = sqrt(max(distSq, 1e-6)); float3 L = toLight / dist; float atten = ComputePointLightAttenuation(worldPos, Lgt); float wrap = 0.35; float NdotLWrap = saturate((dot(N, L) + wrap) / (1.0 + wrap)); float shadow = 1.0; if (Lgt.samples != 0u && NdotLWrap > 0.0001 && atten > 0.0 && dist > 0.01) { shadow = TraceSoftShadow(worldPos, N, Lgt, toLight, dist); } float3 diffuse = Lgt.color * (Lgt.intensity * atten * NdotLWrap * shadow); lightingAccum += diffuse; if (Lgt.pointRadiusPad <= 0.5) { specularAccum += ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, baseAlbedo); } } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) { float3 toLight = Lgt.position - worldPos; float distSq = dot(toLight, toLight); float dist = sqrt(max(distSq, 1e-6)); float3 L = toLight / dist; float atten = ComputeSpotLightAttenuation(worldPos, Lgt); float wrap = 0.35; float NdotLWrap = saturate((dot(N, L) + wrap) / (1.0 + wrap)); float shadow = 1.0; if (Lgt.samples != 0u && NdotLWrap > 0.0001 && atten > 0.0 && dist > 0.01) { shadow = TraceSpotShadow(worldPos, N, toLight, dist); } float3 diffuse = Lgt.color * (Lgt.intensity * atten * NdotLWrap * shadow); lightingAccum += diffuse; if (Lgt.pointRadiusPad <= 0.5) { specularAccum += ComputeSpecular( N, V, L, Lgt.color, Lgt.intensity, atten, shadow, baseAlbedo); } } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) { float3 toCenter = Lgt.position - worldPos; float centerDistSq = dot(toCenter, toCenter); float centerDist = sqrt(max(centerDistSq, 1e-6)); float3 centerDir = toCenter / centerDist; float attenRadius = max(Lgt.radius, 1e-4); float atten = saturate((attenRadius - centerDist) / attenRadius); atten = atten * atten * atten * atten; float shadow = 1.0; if (Lgt.samples != 0u && atten > 0.0 && centerDist > 0.01) { shadow = RectLightShadow(worldPos, N, Lgt, pixel); } uint sampleCount = max(Lgt.samples, 1u); sampleCount = min(sampleCount, 16u); float3 rectDiffuseAccum = 0.0; float3 rectSpecAccum = 0.0; float rand = Hash12((float2)pixel * 0.73 + worldPos.xy + float2(worldPos.z, centerDist)); [loop] for (uint s = 0; s < sampleCount; ++s) { float2 xi = Hammersley2D(s, sampleCount, rand); float2 uv = xi * 2.0 - 1.0; float3 sampleLightPos = Lgt.position + Lgt.axisU * (uv.x * Lgt.halfWidth) + Lgt.axisV * (uv.y * Lgt.halfHeight); float3 sampleVec = sampleLightPos - worldPos; float sampleDistSq = dot(sampleVec, sampleVec); float sampleDist = sqrt(max(sampleDistSq, 1e-6)); float3 L = sampleVec / sampleDist; float NdotL = saturate(dot(N, L)); float faceTerm = (Lgt.twoSided != 0) ? abs(dot(-L, Lgt.normal)) : saturate(dot(-L, Lgt.normal)); float sampleWeight = Lgt.intensity * NdotL * faceTerm; rectDiffuseAccum += Lgt.color * sampleWeight; if (Lgt.pointRadiusPad <= 0.5) { rectSpecAccum += ComputeSpecular( N, V, L, Lgt.color, Lgt.intensity * faceTerm, 1.0, 1.0, baseAlbedo); } } rectDiffuseAccum /= (float)sampleCount; rectSpecAccum /= (float)sampleCount; lightingAccum += clamp(rectDiffuseAccum * atten * shadow, 0.0, 4.0); specularAccum += rectSpecAccum * atten * shadow; } } lightingAccum *= ao; specularAccum *= ao; if (geoFlag == GEOMETRY_FLAG_SKELETAL) { lightingAccum *= 1.2; specularAccum *= 1.15; } if (geoFlag == GEOMETRY_FLAG_UNLIT) { gOutputTex[pixel] = float4(baseAlbedo, albedoSample.a); } else { float3 finalColor = (albedo * lightingAccum) + specularAccum.xyz; gOutputTex[pixel] = float4(finalColor, albedoSample.a); } } )"; static ComPtr glRaytracingLightingCompileLibrary(const char* src) { ComPtr utils; ComPtr compiler; ComPtr includeHandler; HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils)); if (FAILED(hr)) { glRaytracingFatal("DxcCreateInstance utils failed 0x%08X", (unsigned)hr); return nullptr; } hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler)); if (FAILED(hr)) { glRaytracingFatal("DxcCreateInstance compiler failed 0x%08X", (unsigned)hr); return nullptr; } hr = utils->CreateDefaultIncludeHandler(&includeHandler); if (FAILED(hr)) { glRaytracingFatal("CreateDefaultIncludeHandler failed 0x%08X", (unsigned)hr); return nullptr; } DxcBuffer source = {}; source.Ptr = src; source.Size = strlen(src); source.Encoding = DXC_CP_UTF8; const wchar_t* args[] = { L"-T", L"lib_6_3", L"-Zi", L"-Qembed_debug", L"-O3", L"-all_resources_bound" }; ComPtr result; hr = compiler->Compile(&source, args, _countof(args), includeHandler.Get(), IID_PPV_ARGS(&result)); if (FAILED(hr)) { glRaytracingFatal("DXC compile failed 0x%08X", (unsigned)hr); return nullptr; } ComPtr errors; result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr); if (errors && errors->GetStringLength() > 0) { OutputDebugStringA(errors->GetStringPointer()); OutputDebugStringA("\n"); } HRESULT status = S_OK; result->GetStatus(&status); if (FAILED(status)) { glRaytracingFatal("DXIL compile status failed 0x%08X", (unsigned)status); return nullptr; } ComPtr dxil; result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr); return dxil; } static int glRaytracingLightingCreateDescriptorHeap(void) { D3D12_DESCRIPTOR_HEAP_DESC hd = {}; hd.NumDescriptors = 7; hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; hd.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; GLR_CHECK(g_glRaytracingCmd.device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&g_glRaytracingLighting.descriptorHeap))); g_glRaytracingLighting.descriptorStride = g_glRaytracingCmd.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); return 1; } static int glRaytracingLightingCreateRootSignatures(void) { { D3D12_DESCRIPTOR_RANGE ranges[2] = {}; ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; ranges[0].NumDescriptors = 6; ranges[0].BaseShaderRegister = 0; ranges[0].RegisterSpace = 0; ranges[0].OffsetInDescriptorsFromTableStart = 0; ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; ranges[1].NumDescriptors = 1; ranges[1].BaseShaderRegister = 0; ranges[1].RegisterSpace = 0; ranges[1].OffsetInDescriptorsFromTableStart = 0; D3D12_ROOT_PARAMETER params[3] = {}; params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[0].DescriptorTable.NumDescriptorRanges = 1; params[0].DescriptorTable.pDescriptorRanges = &ranges[0]; params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[1].DescriptorTable.NumDescriptorRanges = 1; params[1].DescriptorTable.pDescriptorRanges = &ranges[1]; params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; params[2].Descriptor.ShaderRegister = 0; params[2].Descriptor.RegisterSpace = 0; params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; D3D12_ROOT_SIGNATURE_DESC rsd = {}; rsd.NumParameters = _countof(params); rsd.pParameters = params; rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE; ComPtr sig; ComPtr err; GLR_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err)); GLR_CHECK(g_glRaytracingCmd.device->CreateRootSignature( 0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(&g_glRaytracingLighting.globalRootSig))); } { D3D12_ROOT_SIGNATURE_DESC rsd = {}; rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; ComPtr sig; ComPtr err; GLR_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err)); GLR_CHECK(g_glRaytracingCmd.device->CreateRootSignature( 0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(&g_glRaytracingLighting.localRootSig))); } return 1; } static int glRaytracingLightingCreateBuffers(void) { g_glRaytracingLighting.constantBuffer = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), glRaytracingAlignUp(sizeof(glRaytracingLightingConstants_t), 256), D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); g_glRaytracingLighting.lightBuffer = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), sizeof(glRaytracingLight_t) * GL_RAYTRACING_MAX_LIGHTS, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); return g_glRaytracingLighting.constantBuffer.resource && g_glRaytracingLighting.lightBuffer.resource; } static void glRaytracingLightingUpdateConstants(void) { glRaytracingMapCopy( g_glRaytracingLighting.constantBuffer.resource.Get(), &g_glRaytracingLighting.constants, sizeof(g_glRaytracingLighting.constants)); } static void glRaytracingLightingUpdateLights(void) { if (g_glRaytracingLighting.cpuLights.empty()) return; glRaytracingMapCopy( g_glRaytracingLighting.lightBuffer.resource.Get(), g_glRaytracingLighting.cpuLights.data(), g_glRaytracingLighting.cpuLights.size() * sizeof(glRaytracingLight_t)); } static void glRaytracingLightingCreatePersistentLightSRV(void) { D3D12_SHADER_RESOURCE_VIEW_DESC srv = {}; srv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.Format = DXGI_FORMAT_UNKNOWN; srv.Buffer.FirstElement = 0; srv.Buffer.NumElements = GL_RAYTRACING_MAX_LIGHTS; srv.Buffer.StructureByteStride = sizeof(glRaytracingLight_t); srv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; D3D12_CPU_DESCRIPTOR_HANDLE base = g_glRaytracingLighting.descriptorHeap->GetCPUDescriptorHandleForHeapStart(); g_glRaytracingCmd.device->CreateShaderResourceView( g_glRaytracingLighting.lightBuffer.resource.Get(), &srv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 0)); } static int glRaytracingLightingCreateStateObject(void) { ComPtr dxil = glRaytracingLightingCompileLibrary(g_glRaytracingLightingHlsl); if (!dxil) return 0; D3D12_EXPORT_DESC exports[3] = {}; exports[0].Name = L"RayGen"; exports[1].Name = L"ShadowMiss"; exports[2].Name = L"ShadowClosestHit"; D3D12_DXIL_LIBRARY_DESC libDesc = {}; D3D12_SHADER_BYTECODE libBytecode = {}; libBytecode.pShaderBytecode = dxil->GetBufferPointer(); libBytecode.BytecodeLength = dxil->GetBufferSize(); libDesc.DXILLibrary = libBytecode; libDesc.NumExports = _countof(exports); libDesc.pExports = exports; D3D12_HIT_GROUP_DESC hitGroup = {}; hitGroup.HitGroupExport = L"ShadowHitGroup"; hitGroup.ClosestHitShaderImport = L"ShadowClosestHit"; hitGroup.Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {}; shaderConfig.MaxPayloadSizeInBytes = sizeof(uint32_t); shaderConfig.MaxAttributeSizeInBytes = 8; D3D12_GLOBAL_ROOT_SIGNATURE globalRS = {}; globalRS.pGlobalRootSignature = g_glRaytracingLighting.globalRootSig.Get(); D3D12_LOCAL_ROOT_SIGNATURE localRS = {}; localRS.pLocalRootSignature = g_glRaytracingLighting.localRootSig.Get(); D3D12_STATE_SUBOBJECT subobjects[8] = {}; UINT sub = 0; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY; subobjects[sub].pDesc = &libDesc; ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; subobjects[sub].pDesc = &hitGroup; ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG; subobjects[sub].pDesc = &shaderConfig; ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; subobjects[sub].pDesc = &globalRS; ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE; subobjects[sub].pDesc = &localRS; ++sub; LPCWSTR localExports[] = { L"RayGen", L"ShadowMiss", L"ShadowHitGroup" }; D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION assoc = {}; assoc.pSubobjectToAssociate = &subobjects[4]; assoc.NumExports = _countof(localExports); assoc.pExports = localExports; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION; subobjects[sub].pDesc = &assoc; ++sub; D3D12_RAYTRACING_PIPELINE_CONFIG pipeConfig = {}; pipeConfig.MaxTraceRecursionDepth = 1; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG; subobjects[sub].pDesc = &pipeConfig; ++sub; D3D12_STATE_OBJECT_DESC soDesc = {}; soDesc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE; soDesc.NumSubobjects = sub; soDesc.pSubobjects = subobjects; GLR_CHECK(g_glRaytracingCmd.device->CreateStateObject(&soDesc, IID_PPV_ARGS(&g_glRaytracingLighting.rtStateObject))); GLR_CHECK(g_glRaytracingLighting.rtStateObject.As(&g_glRaytracingLighting.rtStateProps)); return 1; } static int glRaytracingLightingCreateShaderTables(void) { void* raygenId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"RayGen"); void* missId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowMiss"); void* hitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup"); if (!raygenId || !missId || !hitId) { glRaytracingFatal("Failed to fetch shader identifiers"); return 0; } const UINT shaderIdSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; const UINT recordSize = (UINT)glRaytracingAlignUp(shaderIdSize, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT); g_glRaytracingLighting.raygenTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), recordSize, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); g_glRaytracingLighting.missTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), recordSize, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); g_glRaytracingLighting.hitTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), recordSize, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); if (!g_glRaytracingLighting.raygenTable.resource || !g_glRaytracingLighting.missTable.resource || !g_glRaytracingLighting.hitTable.resource) { return 0; } uint8_t temp[256] = {}; memset(temp, 0, sizeof(temp)); memcpy(temp, raygenId, shaderIdSize); glRaytracingMapCopy(g_glRaytracingLighting.raygenTable.resource.Get(), temp, recordSize); memset(temp, 0, sizeof(temp)); memcpy(temp, missId, shaderIdSize); glRaytracingMapCopy(g_glRaytracingLighting.missTable.resource.Get(), temp, recordSize); memset(temp, 0, sizeof(temp)); memcpy(temp, hitId, shaderIdSize); glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp, recordSize); return 1; } static void glRaytracingLightingCreatePerPassDescriptors( const glRaytracingLightingPassDesc_t* pass, ID3D12Resource* topLevelAS) { D3D12_CPU_DESCRIPTOR_HANDLE base = g_glRaytracingLighting.descriptorHeap->GetCPUDescriptorHandleForHeapStart(); D3D12_SHADER_RESOURCE_VIEW_DESC albedoSrv = {}; albedoSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; albedoSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; albedoSrv.Format = pass->albedoFormat; albedoSrv.Texture2D.MipLevels = 1; g_glRaytracingCmd.device->CreateShaderResourceView( pass->albedoTexture, &albedoSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 1)); D3D12_SHADER_RESOURCE_VIEW_DESC depthSrv = {}; depthSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; depthSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; depthSrv.Format = glRaytracingGetSrvFormatForDepth(pass->depthFormat); depthSrv.Texture2D.MipLevels = 1; g_glRaytracingCmd.device->CreateShaderResourceView( pass->depthTexture, &depthSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 2)); D3D12_SHADER_RESOURCE_VIEW_DESC normalSrv = {}; normalSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; normalSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; normalSrv.Format = pass->normalFormat; normalSrv.Texture2D.MipLevels = 1; g_glRaytracingCmd.device->CreateShaderResourceView( pass->normalTexture, &normalSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 3)); D3D12_SHADER_RESOURCE_VIEW_DESC positionSrv = {}; positionSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; positionSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; positionSrv.Format = pass->positionFormat; positionSrv.Texture2D.MipLevels = 1; g_glRaytracingCmd.device->CreateShaderResourceView( pass->positionTexture, &positionSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 4)); D3D12_SHADER_RESOURCE_VIEW_DESC tlasSrv = {}; tlasSrv.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE; tlasSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; tlasSrv.RaytracingAccelerationStructure.Location = topLevelAS->GetGPUVirtualAddress(); g_glRaytracingCmd.device->CreateShaderResourceView( nullptr, &tlasSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 5)); D3D12_UNORDERED_ACCESS_VIEW_DESC outputUav = {}; outputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; outputUav.Format = pass->outputFormat; g_glRaytracingCmd.device->CreateUnorderedAccessView( pass->outputTexture, nullptr, &outputUav, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, 6)); } // ============================================================ // Lighting public API // ============================================================ static bool glRaytracingLightingExecuteInternal( const glRaytracingLightingPassDesc_t* pass, ID3D12Resource* topLevelAS) { if (!g_glRaytracingLighting.initialized || !pass || !topLevelAS) return false; if (!pass->albedoTexture || !pass->depthTexture || !pass->normalTexture || !pass->positionTexture || !pass->outputTexture) { return false; } if (pass->width == 0 || pass->height == 0) return false; g_glRaytracingLighting.constants.screenSize[0] = (float)pass->width; g_glRaytracingLighting.constants.screenSize[1] = (float)pass->height; g_glRaytracingLighting.constants.screenSize[2] = 1.0f / (float)pass->width; g_glRaytracingLighting.constants.screenSize[3] = 1.0f / (float)pass->height; g_glRaytracingLighting.constants.lightCount = (uint32_t)glRaytracingClamp(g_glRaytracingLighting.cpuLights.size(), 0, GL_RAYTRACING_MAX_LIGHTS); glRaytracingLightingUpdateLights(); glRaytracingLightingUpdateConstants(); glRaytracingLightingCreatePerPassDescriptors(pass, topLevelAS); if (!glRaytracingBeginCmd()) return false; glRaytracingTransition( g_glRaytracingCmd.cmdList.Get(), pass->outputTexture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); ID3D12DescriptorHeap* heaps[] = { g_glRaytracingLighting.descriptorHeap.Get() }; g_glRaytracingCmd.cmdList->SetDescriptorHeaps(_countof(heaps), heaps); g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get()); D3D12_GPU_DESCRIPTOR_HANDLE gpuBase = g_glRaytracingLighting.descriptorHeap->GetGPUDescriptorHandleForHeapStart(); g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable( 0, glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, 0)); g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable( 1, glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, 6)); g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView( 2, g_glRaytracingLighting.constantBuffer.gpuVA); g_glRaytracingCmd.cmdList->SetPipelineState1(g_glRaytracingLighting.rtStateObject.Get()); const UINT shaderRecordSize = (UINT)glRaytracingAlignUp( D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT); D3D12_DISPATCH_RAYS_DESC rays = {}; rays.RayGenerationShaderRecord.StartAddress = g_glRaytracingLighting.raygenTable.gpuVA; rays.RayGenerationShaderRecord.SizeInBytes = shaderRecordSize; rays.MissShaderTable.StartAddress = g_glRaytracingLighting.missTable.gpuVA; rays.MissShaderTable.SizeInBytes = shaderRecordSize; rays.MissShaderTable.StrideInBytes = shaderRecordSize; rays.HitGroupTable.StartAddress = g_glRaytracingLighting.hitTable.gpuVA; rays.HitGroupTable.SizeInBytes = shaderRecordSize; rays.HitGroupTable.StrideInBytes = shaderRecordSize; rays.Width = pass->width; rays.Height = pass->height; rays.Depth = 1; g_glRaytracingCmd.cmdList->DispatchRays(&rays); D3D12_RESOURCE_BARRIER uav = {}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = pass->outputTexture; g_glRaytracingCmd.cmdList->ResourceBarrier(1, &uav); // Do not copy to QD3D12_GetCurrentBackBuffer() here. In multi-window // mode that global/current back buffer can belong to a different window. // The caller should copy/present pass->outputTexture in the correct // per-window context after this function returns. glRaytracingTransition( g_glRaytracingCmd.cmdList.Get(), pass->outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); if (!glRaytracingEndCmd()) return false; return true; } static ID3D12Resource* glRaytracingResolveTLASForWorld(glRaytracingSceneHandle_t worldHandle) { glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle); if (!world) return nullptr; if (!glRaytracingBuildDirtyMeshesInternal()) return nullptr; if (!glRaytracingBuildSceneInternal(world)) return nullptr; if (!world->tlasBuilt) return nullptr; return glRaytracingGetCurrentTLASBuffer(world)->resource.Get(); } bool glRaytracingLightingInit(void) { std::lock_guard lock(g_glRaytracingMutex); if (g_glRaytracingLighting.initialized) return true; if (!glRaytracingInitCmdContext()) return false; if (!glRaytracingLightingCreateDescriptorHeap()) return false; if (!glRaytracingLightingCreateRootSignatures()) return false; if (!glRaytracingLightingCreateBuffers()) return false; glRaytracingLightingCreatePersistentLightSRV(); if (!glRaytracingLightingCreateStateObject()) return false; if (!glRaytracingLightingCreateShaderTables()) return false; memset(&g_glRaytracingLighting.constants, 0, sizeof(g_glRaytracingLighting.constants)); g_glRaytracingLighting.constants.ambientColor[0] = 0.08f; g_glRaytracingLighting.constants.ambientColor[1] = 0.08f; g_glRaytracingLighting.constants.ambientColor[2] = 0.09f; g_glRaytracingLighting.constants.ambientColor[3] = 1.0f; g_glRaytracingLighting.constants.enableSpecular = 1; g_glRaytracingLighting.constants.enableHalfLambert = 1; g_glRaytracingLighting.constants.normalReconstructZ = 1.0f; g_glRaytracingLighting.constants.shadowBias = 1.5f; glRaytracingLightingUpdateConstants(); g_glRaytracingLighting.initialized = true; glRaytracingLog("glRaytracingLightingInit ok"); return true; } void glRaytracingLightingShutdown(void) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingLighting.initialized) return; g_glRaytracingLighting = glRaytracingLightingState_t(); } bool glRaytracingLightingIsInitialized(void) { std::lock_guard lock(g_glRaytracingMutex); return g_glRaytracingLighting.initialized; } void glRaytracingLightingClearLights(bool clearPersistant) { std::lock_guard lock(g_glRaytracingMutex); if (clearPersistant) { g_glRaytracingLighting.cpuLights.clear(); } else { size_t writeIndex = 0; for (size_t i = 0; i < g_glRaytracingLighting.cpuLights.size(); ++i) { if (g_glRaytracingLighting.cpuLights[i].persistant) { if (writeIndex != i) { g_glRaytracingLighting.cpuLights[writeIndex] = g_glRaytracingLighting.cpuLights[i]; } ++writeIndex; } } g_glRaytracingLighting.cpuLights.resize(writeIndex); } g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size(); glRaytracingLightingUpdateConstants(); } bool glRaytracingLightingAddLight(const glRaytracingLight_t* light) { std::lock_guard lock(g_glRaytracingMutex); if (!g_glRaytracingLighting.initialized || !light) return false; if (g_glRaytracingLighting.cpuLights.size() >= GL_RAYTRACING_MAX_LIGHTS) return false; g_glRaytracingLighting.cpuLights.push_back(*light); g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size(); glRaytracingLightingUpdateLights(); glRaytracingLightingUpdateConstants(); return true; } void glRaytracingLightingSetAmbient(float r, float g, float b, float intensity) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.ambientColor[0] = r; g_glRaytracingLighting.constants.ambientColor[1] = g; g_glRaytracingLighting.constants.ambientColor[2] = b; g_glRaytracingLighting.constants.ambientColor[3] = intensity; glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetCameraPosition(float x, float y, float z) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.cameraPos[0] = x; g_glRaytracingLighting.constants.cameraPos[1] = y; g_glRaytracingLighting.constants.cameraPos[2] = z; g_glRaytracingLighting.constants.cameraPos[3] = 1.0f; glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetInvViewProjMatrix(const float* m16) { std::lock_guard lock(g_glRaytracingMutex); if (!m16) return; memcpy(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16); glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetInvViewMatrix(const float* m16) { std::lock_guard lock(g_glRaytracingMutex); if (!m16) return; memcpy(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16); glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetNormalReconstructSign(float signValue) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.normalReconstructZ = signValue; glRaytracingLightingUpdateConstants(); } void glRaytracingLightingEnableSpecular(int enable) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.enableSpecular = enable ? 1u : 0u; glRaytracingLightingUpdateConstants(); } void glRaytracingLightingEnableHalfLambert(int enable) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.enableHalfLambert = enable ? 1u : 0u; glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetShadowBias(float bias) { std::lock_guard lock(g_glRaytracingMutex); g_glRaytracingLighting.constants.shadowBias = bias; glRaytracingLightingUpdateConstants(); } bool glRaytracingLightingExecuteForScene(const glRaytracingLightingPassDesc_t* pass, glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); ID3D12Resource* topLevelAS = glRaytracingResolveTLASForWorld(worldHandle); if (!topLevelAS) return false; return glRaytracingLightingExecuteInternal(pass, topLevelAS); } glRaytracingLight_t glRaytracingLightingMakePointLight( float px, float py, float pz, float radiusX, float radiusY, float radiusZ, float r, float g, float b, float intensity) { glRaytracingLight_t l = {}; float ax = (radiusX < 0.0f) ? -radiusX : radiusX; float ay = (radiusY < 0.0f) ? -radiusY : radiusY; float az = (radiusZ < 0.0f) ? -radiusZ : radiusZ; float maxRadius = ax; if (ay > maxRadius) maxRadius = ay; if (az > maxRadius) maxRadius = az; if (maxRadius <= 0.0f) maxRadius = 1e-4f; if (ax <= 0.0f) ax = maxRadius; if (ay <= 0.0f) ay = maxRadius; if (az <= 0.0f) az = maxRadius; l.position.x = px; l.position.y = py; l.position.z = pz; // Keep radius populated as a scalar fallback/max range, but point lights now // attenuate using pointRadius.x/y/z in the ray generation shader. l.radius = maxRadius; l.pointRadius.x = ax; l.pointRadius.y = ay; l.pointRadius.z = az; l.pointRadiusPad = 0.0f; l.color.x = r; l.color.y = g; l.color.z = b; l.intensity = intensity; l.normal.x = 0.0f; l.normal.y = 0.0f; l.normal.z = 1.0f; l.type = GL_RAYTRACING_LIGHT_TYPE_POINT; l.axisU.x = 1.0f; l.axisU.y = 0.0f; l.axisU.z = 0.0f; l.halfWidth = 0.0f; l.axisV.x = 0.0f; l.axisV.y = 1.0f; l.axisV.z = 0.0f; l.halfHeight = 0.0f; l.samples = 1; l.twoSided = 0; l.persistant = 0.0f; l.pad1 = 0.0f; return l; } glRaytracingLight_t glRaytracingLightingMakeSpotLight( float px, float py, float pz, float dx, float dy, float dz, float ux, float uy, float uz, float vx, float vy, float vz, float nearPlane, float farPlane, float tanHalfWidth, float tanHalfHeight, float r, float g, float b, float intensity, uint32_t samples) { glRaytracingLight_t l = {}; glRaytracingNormalize3(dx, dy, dz); // Make U perpendicular to D. { const float du = dx * ux + dy * uy + dz * uz; ux -= dx * du; uy -= dy * du; uz -= dz * du; const float uLenSq = ux * ux + uy * uy + uz * uz; if (uLenSq <= 1e-20f) { const float absDz = (dz < 0.0f) ? -dz : dz; if (absDz < 0.999f) { glRaytracingCross3(0.0f, 0.0f, 1.0f, dx, dy, dz, ux, uy, uz); } else { glRaytracingCross3(0.0f, 1.0f, 0.0f, dx, dy, dz, ux, uy, uz); } } glRaytracingNormalize3(ux, uy, uz); } // Rebuild V from D x U so the basis is orthonormal, while preserving the // sign of the caller-provided V whenever possible. { float builtVx, builtVy, builtVz; glRaytracingCross3(dx, dy, dz, ux, uy, uz, builtVx, builtVy, builtVz); glRaytracingNormalize3(builtVx, builtVy, builtVz); const float sign = builtVx * vx + builtVy * vy + builtVz * vz; if (sign < 0.0f) { builtVx = -builtVx; builtVy = -builtVy; builtVz = -builtVz; } vx = builtVx; vy = builtVy; vz = builtVz; } if (nearPlane < 0.0f) nearPlane = 0.0f; if (farPlane <= nearPlane) farPlane = nearPlane + 1e-3f; if (tanHalfWidth < 0.0f) tanHalfWidth = -tanHalfWidth; if (tanHalfHeight < 0.0f) tanHalfHeight = -tanHalfHeight; if (tanHalfWidth <= 1e-4f) tanHalfWidth = 1e-4f; if (tanHalfHeight <= 1e-4f) tanHalfHeight = 1e-4f; l.position.x = px; l.position.y = py; l.position.z = pz; // For spot lights, radius stores the far clip distance while pointRadius.x // stores the near clip distance. l.radius = farPlane; l.pointRadius.x = nearPlane; l.pointRadius.y = 0.0f; l.pointRadius.z = 0.0f; l.pointRadiusPad = 0.0f; l.color.x = r; l.color.y = g; l.color.z = b; l.intensity = intensity; l.normal.x = dx; l.normal.y = dy; l.normal.z = dz; l.type = GL_RAYTRACING_LIGHT_TYPE_SPOT; l.axisU.x = ux; l.axisU.y = uy; l.axisU.z = uz; l.halfWidth = tanHalfWidth; l.axisV.x = vx; l.axisV.y = vy; l.axisV.z = vz; l.halfHeight = tanHalfHeight; l.samples = samples ? samples : 1u; l.twoSided = 0; l.persistant = 0.0f; l.pad1 = 0.0f; return l; } glRaytracingLight_t glRaytracingLightingMakeRectLight( float px, float py, float pz, float nx, float ny, float nz, float ux, float uy, float uz, float vx, float vy, float vz, float halfWidth, float halfHeight, float r, float g, float b, float intensity, uint32_t samples, uint32_t twoSided) { glRaytracingLight_t l = {}; glRaytracingNormalize3(nx, ny, nz); glRaytracingNormalize3(ux, uy, uz); glRaytracingNormalize3(vx, vy, vz); if ((nx == 0.0f && ny == 0.0f && nz == 0.0f) && !((ux == 0.0f && uy == 0.0f && uz == 0.0f) || (vx == 0.0f && vy == 0.0f && vz == 0.0f))) { glRaytracingCross3(ux, uy, uz, vx, vy, vz, nx, ny, nz); glRaytracingNormalize3(nx, ny, nz); } l.position.x = px; l.position.y = py; l.position.z = pz; // Reuse radius as influence/falloff range for the rect light. l.radius = (halfWidth > halfHeight ? halfWidth : halfHeight) * 6.0f; l.pointRadius.x = l.radius; l.pointRadius.y = l.radius; l.pointRadius.z = l.radius; l.pointRadiusPad = 0.0f; l.color.x = r; l.color.y = g; l.color.z = b; l.intensity = intensity; l.normal.x = nx; l.normal.y = ny; l.normal.z = nz; l.type = GL_RAYTRACING_LIGHT_TYPE_RECT; l.axisU.x = ux; l.axisU.y = uy; l.axisU.z = uz; l.halfWidth = halfWidth; l.axisV.x = vx; l.axisV.y = vy; l.axisV.z = vz; l.halfHeight = halfHeight; l.samples = samples ? samples : 4u; l.twoSided = twoSided ? 1u : 0u; l.persistant = 0.0f; l.pad1 = 0.0f; return l; } uint32_t glRaytracingLightingGetLightCount(void) { return (uint32_t)g_glRaytracingLighting.cpuLights.size(); }