mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 00:01:49 +02:00
7245 lines
224 KiB
C++
7245 lines
224 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
|
|
|
|
IceTech Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
IceTech Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "opengl.h"
|
|
|
|
#include <windows.h>
|
|
#include <d3d12.h>
|
|
#include <dxgi1_6.h>
|
|
#include <d3dcompiler.h>
|
|
#include <dxcapi.h>
|
|
#include <wrl/client.h>
|
|
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
#pragma comment(lib, "dxcompiler.lib")
|
|
#pragma comment(lib, "d3dcompiler.lib")
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
extern float com_pathTracingGpuMsec;
|
|
|
|
// ============================================================
|
|
// 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<typename T>
|
|
static T glRaytracingClamp(T v, T lo, T hi)
|
|
{
|
|
return (v < lo) ? lo : ((v > hi) ? hi : v);
|
|
}
|
|
|
|
static void glRaytracingSetIdentity4x4(float* m)
|
|
{
|
|
if (!m)
|
|
return;
|
|
|
|
memset(m, 0, sizeof(float) * 16);
|
|
m[0] = 1.0f;
|
|
m[5] = 1.0f;
|
|
m[10] = 1.0f;
|
|
m[15] = 1.0f;
|
|
}
|
|
|
|
static int glRaytracingInvertMatrix4x4(const float* m, float* out)
|
|
{
|
|
if (!m || !out)
|
|
return 0;
|
|
|
|
float a[4][8];
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
for (int c = 0; c < 4; ++c)
|
|
a[r][c] = m[r * 4 + c];
|
|
|
|
for (int c = 0; c < 4; ++c)
|
|
a[r][4 + c] = (r == c) ? 1.0f : 0.0f;
|
|
}
|
|
|
|
for (int col = 0; col < 4; ++col)
|
|
{
|
|
int pivot = col;
|
|
float best = a[col][col] < 0.0f ? -a[col][col] : a[col][col];
|
|
|
|
for (int r = col + 1; r < 4; ++r)
|
|
{
|
|
const float v = a[r][col] < 0.0f ? -a[r][col] : a[r][col];
|
|
if (v > best)
|
|
{
|
|
best = v;
|
|
pivot = r;
|
|
}
|
|
}
|
|
|
|
if (best <= 1.0e-8f)
|
|
return 0;
|
|
|
|
if (pivot != col)
|
|
{
|
|
for (int c = 0; c < 8; ++c)
|
|
{
|
|
const float tmp = a[col][c];
|
|
a[col][c] = a[pivot][c];
|
|
a[pivot][c] = tmp;
|
|
}
|
|
}
|
|
|
|
const float invPivot = 1.0f / a[col][col];
|
|
for (int c = 0; c < 8; ++c)
|
|
a[col][c] *= invPivot;
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
if (r == col)
|
|
continue;
|
|
|
|
const float f = a[r][col];
|
|
if (f == 0.0f)
|
|
continue;
|
|
|
|
for (int c = 0; c < 8; ++c)
|
|
a[r][c] -= f * a[col][c];
|
|
}
|
|
}
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
for (int c = 0; c < 4; ++c)
|
|
out[r * 4 + c] = a[r][4 + c];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
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<ID3D12Resource> 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;
|
|
}
|
|
|
|
struct glRaytracingTexture_t
|
|
{
|
|
ComPtr<ID3D12Resource> resource;
|
|
UINT width;
|
|
UINT height;
|
|
DXGI_FORMAT format;
|
|
D3D12_RESOURCE_STATES state;
|
|
|
|
glRaytracingTexture_t()
|
|
{
|
|
width = 0;
|
|
height = 0;
|
|
format = DXGI_FORMAT_UNKNOWN;
|
|
state = D3D12_RESOURCE_STATE_COMMON;
|
|
}
|
|
};
|
|
|
|
static glRaytracingTexture_t glRaytracingCreateTexture2D(
|
|
ID3D12Device* device,
|
|
UINT width,
|
|
UINT height,
|
|
DXGI_FORMAT format,
|
|
D3D12_RESOURCE_STATES initialState,
|
|
D3D12_RESOURCE_FLAGS flags)
|
|
{
|
|
glRaytracingTexture_t out;
|
|
|
|
D3D12_HEAP_PROPERTIES hp = {};
|
|
hp.Type = D3D12_HEAP_TYPE_DEFAULT;
|
|
|
|
D3D12_RESOURCE_DESC rd = {};
|
|
rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
rd.Width = width;
|
|
rd.Height = height;
|
|
rd.DepthOrArraySize = 1;
|
|
rd.MipLevels = 1;
|
|
rd.Format = format;
|
|
rd.SampleDesc.Count = 1;
|
|
rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
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 texture failed 0x%08X", (unsigned)hr);
|
|
return out;
|
|
}
|
|
|
|
out.width = width;
|
|
out.height = height;
|
|
out.format = format;
|
|
out.state = initialState;
|
|
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
|
|
// ============================================================
|
|
|
|
#ifndef GL_RAYTRACING_CMD_RING_SIZE
|
|
#define GL_RAYTRACING_CMD_RING_SIZE 4
|
|
#endif
|
|
|
|
#if GL_RAYTRACING_CMD_RING_SIZE < 2
|
|
#error GL_RAYTRACING_CMD_RING_SIZE must be at least 2
|
|
#endif
|
|
|
|
#ifndef GL_RAYTRACING_FORCE_CPU_SYNC
|
|
#define GL_RAYTRACING_FORCE_CPU_SYNC 0
|
|
#endif
|
|
|
|
struct glRaytracingCmdContext_t
|
|
{
|
|
ComPtr<ID3D12Device5> device;
|
|
ComPtr<ID3D12CommandQueue> queue;
|
|
|
|
// Active command allocator/list for the command currently being recorded.
|
|
// The ring below avoids the old every-frame CPU/GPU fence wait.
|
|
ComPtr<ID3D12CommandAllocator> cmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> cmdList;
|
|
UINT64 cmdLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> cmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> cmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 cmdFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT cmdRingIndex;
|
|
UINT cmdCurrentSlot;
|
|
|
|
ComPtr<ID3D12CommandAllocator> blasCmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> blasCmdList;
|
|
UINT64 blasLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> blasCmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> blasCmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 blasFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT blasRingIndex;
|
|
UINT blasCurrentSlot;
|
|
|
|
ComPtr<ID3D12CommandAllocator> tlasCmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> tlasCmdList;
|
|
UINT64 tlasLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> tlasCmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> tlasCmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 tlasFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT tlasRingIndex;
|
|
UINT tlasCurrentSlot;
|
|
|
|
ComPtr<ID3D12Fence> fence;
|
|
HANDLE fenceEvent;
|
|
UINT64 nextFenceValue;
|
|
ComPtr<ID3D12QueryHeap> pathTracingTimestampHeap;
|
|
glRaytracingBuffer_t pathTracingTimestampReadback;
|
|
UINT64 pathTracingTimestampFrequency;
|
|
UINT64 pathTracingTimestampFenceRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
bool initialized;
|
|
|
|
glRaytracingCmdContext_t()
|
|
{
|
|
cmdLastFenceValue = 0;
|
|
blasLastFenceValue = 0;
|
|
tlasLastFenceValue = 0;
|
|
cmdRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
blasRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
tlasRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
cmdCurrentSlot = 0;
|
|
blasCurrentSlot = 0;
|
|
tlasCurrentSlot = 0;
|
|
for (UINT i = 0; i < GL_RAYTRACING_CMD_RING_SIZE; ++i)
|
|
{
|
|
cmdFenceValueRing[i] = 0;
|
|
blasFenceValueRing[i] = 0;
|
|
tlasFenceValueRing[i] = 0;
|
|
pathTracingTimestampFenceRing[i] = 0;
|
|
}
|
|
fenceEvent = nullptr;
|
|
nextFenceValue = 0;
|
|
pathTracingTimestampFrequency = 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 void glRaytracingPollPathTracingTimestamp(UINT slot)
|
|
{
|
|
if (!g_glRaytracingCmd.pathTracingTimestampReadback.resource ||
|
|
!g_glRaytracingCmd.pathTracingTimestampFrequency ||
|
|
slot >= GL_RAYTRACING_CMD_RING_SIZE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UINT64 fenceValue = g_glRaytracingCmd.pathTracingTimestampFenceRing[slot];
|
|
if (!fenceValue || !g_glRaytracingCmd.fence || g_glRaytracingCmd.fence->GetCompletedValue() < fenceValue)
|
|
return;
|
|
|
|
const UINT64 offset = sizeof(UINT64) * 2ull * slot;
|
|
D3D12_RANGE readRange = { offset, offset + sizeof(UINT64) * 2ull };
|
|
UINT64* timestamps = nullptr;
|
|
if (SUCCEEDED(g_glRaytracingCmd.pathTracingTimestampReadback.resource->Map(0, &readRange, reinterpret_cast<void**>(×tamps))) && timestamps != nullptr)
|
|
{
|
|
const UINT64 begin = timestamps[slot * 2 + 0];
|
|
const UINT64 end = timestamps[slot * 2 + 1];
|
|
if (end >= begin)
|
|
{
|
|
com_pathTracingGpuMsec = (float)((double)(end - begin) * 1000.0 / (double)g_glRaytracingCmd.pathTracingTimestampFrequency);
|
|
}
|
|
|
|
D3D12_RANGE writeRange = { 0, 0 };
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource->Unmap(0, &writeRange);
|
|
}
|
|
g_glRaytracingCmd.pathTracingTimestampFenceRing[slot] = 0;
|
|
}
|
|
|
|
static int glRaytracingCreatePathTracingTimestamps(void)
|
|
{
|
|
D3D12_QUERY_HEAP_DESC qh = {};
|
|
qh.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
|
|
qh.NumDescriptors = GL_RAYTRACING_CMD_RING_SIZE * 2;
|
|
if (FAILED(g_glRaytracingCmd.device->CreateQueryHeap(&qh, IID_PPV_ARGS(&g_glRaytracingCmd.pathTracingTimestampHeap))))
|
|
return 0;
|
|
|
|
g_glRaytracingCmd.pathTracingTimestampReadback = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
sizeof(UINT64) * GL_RAYTRACING_CMD_RING_SIZE * 2ull,
|
|
D3D12_HEAP_TYPE_READBACK,
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
if (!g_glRaytracingCmd.pathTracingTimestampReadback.resource)
|
|
return 0;
|
|
|
|
g_glRaytracingCmd.pathTracingTimestampFrequency = 0;
|
|
if (FAILED(g_glRaytracingCmd.queue->GetTimestampFrequency(&g_glRaytracingCmd.pathTracingTimestampFrequency)) ||
|
|
g_glRaytracingCmd.pathTracingTimestampFrequency == 0)
|
|
{
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Reset();
|
|
g_glRaytracingCmd.pathTracingTimestampReadback = glRaytracingBuffer_t();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingCreateDirectCommandListPair(
|
|
ID3D12Device5* device,
|
|
ComPtr<ID3D12CommandAllocator>& allocator,
|
|
ComPtr<ID3D12GraphicsCommandList4>& list)
|
|
{
|
|
GLR_CHECK(device->CreateCommandAllocator(
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
IID_PPV_ARGS(&allocator)));
|
|
|
|
GLR_CHECK(device->CreateCommandList(
|
|
0,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
allocator.Get(),
|
|
nullptr,
|
|
IID_PPV_ARGS(&list)));
|
|
|
|
GLR_CHECK(list->Close());
|
|
return 1;
|
|
}
|
|
|
|
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<ID3D12Device5> 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;
|
|
|
|
for (UINT i = 0; i < GL_RAYTRACING_CMD_RING_SIZE; ++i)
|
|
{
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.cmdAllocRing[i],
|
|
g_glRaytracingCmd.cmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.blasCmdAllocRing[i],
|
|
g_glRaytracingCmd.blasCmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.tlasCmdAllocRing[i],
|
|
g_glRaytracingCmd.tlasCmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
g_glRaytracingCmd.cmdAlloc = g_glRaytracingCmd.cmdAllocRing[0];
|
|
g_glRaytracingCmd.cmdList = g_glRaytracingCmd.cmdListRing[0];
|
|
g_glRaytracingCmd.blasCmdAlloc = g_glRaytracingCmd.blasCmdAllocRing[0];
|
|
g_glRaytracingCmd.blasCmdList = g_glRaytracingCmd.blasCmdListRing[0];
|
|
g_glRaytracingCmd.tlasCmdAlloc = g_glRaytracingCmd.tlasCmdAllocRing[0];
|
|
g_glRaytracingCmd.tlasCmdList = g_glRaytracingCmd.tlasCmdListRing[0];
|
|
|
|
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;
|
|
}
|
|
|
|
if (!glRaytracingCreatePathTracingTimestamps())
|
|
{
|
|
glRaytracingLog("path tracing timestamp queries unavailable");
|
|
}
|
|
|
|
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)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.cmdRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdFenceValueRing[slot]);
|
|
glRaytracingPollPathTracingTimestamp(slot);
|
|
|
|
g_glRaytracingCmd.cmdRingIndex = slot;
|
|
g_glRaytracingCmd.cmdCurrentSlot = slot;
|
|
g_glRaytracingCmd.cmdAlloc = g_glRaytracingCmd.cmdAllocRing[slot];
|
|
g_glRaytracingCmd.cmdList = g_glRaytracingCmd.cmdListRing[slot];
|
|
|
|
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();
|
|
g_glRaytracingCmd.cmdFenceValueRing[g_glRaytracingCmd.cmdCurrentSlot] = g_glRaytracingCmd.cmdLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdLastFenceValue);
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingBeginBlasCmd(void)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.blasRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.blasFenceValueRing[slot]);
|
|
|
|
g_glRaytracingCmd.blasRingIndex = slot;
|
|
g_glRaytracingCmd.blasCurrentSlot = slot;
|
|
g_glRaytracingCmd.blasCmdAlloc = g_glRaytracingCmd.blasCmdAllocRing[slot];
|
|
g_glRaytracingCmd.blasCmdList = g_glRaytracingCmd.blasCmdListRing[slot];
|
|
|
|
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();
|
|
g_glRaytracingCmd.blasFenceValueRing[g_glRaytracingCmd.blasCurrentSlot] = g_glRaytracingCmd.blasLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.blasLastFenceValue);
|
|
#endif
|
|
|
|
return g_glRaytracingCmd.blasLastFenceValue;
|
|
}
|
|
|
|
static int glRaytracingBeginTlasCmd(void)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.tlasRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.tlasFenceValueRing[slot]);
|
|
|
|
g_glRaytracingCmd.tlasRingIndex = slot;
|
|
g_glRaytracingCmd.tlasCurrentSlot = slot;
|
|
g_glRaytracingCmd.tlasCmdAlloc = g_glRaytracingCmd.tlasCmdAllocRing[slot];
|
|
g_glRaytracingCmd.tlasCmdList = g_glRaytracingCmd.tlasCmdListRing[slot];
|
|
|
|
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();
|
|
g_glRaytracingCmd.tlasFenceValueRing[g_glRaytracingCmd.tlasCurrentSlot] = g_glRaytracingCmd.tlasLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.tlasLastFenceValue);
|
|
#endif
|
|
|
|
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
|
|
|
|
// Internal material metadata encoded into D3D12's 24-bit shader-visible
|
|
// InstanceID. Lower 16 bits remain caller/user ID; bits 16-23 are material flags.
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_USER_ID_MASK = 0x0000FFFFu;
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT = 16u;
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_MATERIAL_MASK = 0x000000FFu;
|
|
|
|
struct glRaytracingMeshRecord_t
|
|
{
|
|
uint32_t handle;
|
|
int alive;
|
|
|
|
glRaytracingMeshDesc_t descCpu;
|
|
|
|
std::vector<glRaytracingVertex_t> verticesCpu;
|
|
std::vector<uint32_t> indicesCpu;
|
|
|
|
glRaytracingBuffer_t vertexBuffer;
|
|
glRaytracingBuffer_t indexBuffer;
|
|
|
|
glRaytracingBuffer_t blasScratch;
|
|
glRaytracingBuffer_t blasResult[2];
|
|
|
|
UINT64 blasScratchSize;
|
|
UINT64 blasResultSize;
|
|
|
|
int blasBuilt;
|
|
int dirty;
|
|
UINT64 blasBuildFenceValue;
|
|
int currentBlasIndex;
|
|
uint32_t materialFlags;
|
|
|
|
glRaytracingMeshRecord_t()
|
|
{
|
|
handle = 0;
|
|
alive = 0;
|
|
memset(&descCpu, 0, sizeof(descCpu));
|
|
blasScratchSize = 0;
|
|
blasResultSize = 0;
|
|
blasBuilt = 0;
|
|
dirty = 0;
|
|
blasBuildFenceValue = 0;
|
|
currentBlasIndex = 0;
|
|
materialFlags = 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<glRaytracingInstanceRecord_t> instances;
|
|
std::vector<int> activeInstanceIndices;
|
|
std::vector<D3D12_RAYTRACING_INSTANCE_DESC> cpuInstanceDescs;
|
|
std::vector<int> instanceHandleToIndex;
|
|
|
|
uint32_t nextInstanceHandle;
|
|
|
|
glRaytracingSceneUploadBuffer_t instanceDescUpload[2];
|
|
UINT64 instanceDescUploadFenceValue[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;
|
|
instanceDescUploadFenceValue[0] = 0;
|
|
instanceDescUploadFenceValue[1] = 0;
|
|
tlasScratchSize = 0;
|
|
tlasResultSize = 0;
|
|
activeInstanceCount = 0;
|
|
builtInstanceCount = 0;
|
|
tlasBuilt = 0;
|
|
tlasNeedsRebuild = 1;
|
|
tlasNeedsUpdate = 1;
|
|
currentTLASIndex = 0;
|
|
}
|
|
};
|
|
|
|
struct glRaytracingSceneState_t
|
|
{
|
|
std::vector<glRaytracingMeshRecord_t> meshes;
|
|
std::vector<int> 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 glRaytracingLightingResetDenoiseHistory(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->instanceDescUploadFenceValue[0] = 0;
|
|
world->instanceDescUploadFenceValue[1] = 0;
|
|
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->instanceDescUploadFenceValue[0] = 0;
|
|
world->instanceDescUploadFenceValue[1] = 0;
|
|
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;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
static void glRaytracingMarkWorldNeedsUpdate(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world || !world->alive)
|
|
return;
|
|
|
|
if (!world->tlasNeedsRebuild)
|
|
world->tlasNeedsUpdate = 1;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
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);
|
|
|
|
const bool resizingScratch = world->tlasScratch.resource &&
|
|
world->tlasScratchSize < requiredScratch;
|
|
const bool resizingResult = (world->tlasResult[0].resource || world->tlasResult[1].resource) &&
|
|
world->tlasResultSize < requiredResult;
|
|
if (resizingScratch || resizingResult)
|
|
{
|
|
// Releasing/reallocating a TLAS resource can invalidate an in-flight ray
|
|
// dispatch that still references the old resource. This resize path is rare,
|
|
// so prefer correctness over complex deferred destruction.
|
|
glRaytracingWaitIdle();
|
|
}
|
|
|
|
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)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
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)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
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)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
mesh->blasResult[1].resource.Reset();
|
|
}
|
|
|
|
mesh->blasResultSize = requiredResult;
|
|
return 1;
|
|
}
|
|
|
|
static inline void glRaytracingBuildInstanceDesc(
|
|
D3D12_RAYTRACING_INSTANCE_DESC* outDesc,
|
|
const glRaytracingInstanceRecord_t& inst,
|
|
uint32_t meshMaterialFlags,
|
|
D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA)
|
|
{
|
|
memcpy(outDesc->Transform, inst.descCpu.transform, sizeof(float) * 12);
|
|
|
|
// InstanceID is 24-bit in D3D12_RAYTRACING_INSTANCE_DESC. Preserve the
|
|
// caller's lower 16 bits, then pack material flags into bits 16-23 so the
|
|
// any-hit shader can decide whether visibility rays pass through the hit.
|
|
//
|
|
// Accept both sources:
|
|
// - meshMaterialFlags set by glRaytracingSetMeshMaterialFlags()/shim mesh tags
|
|
// - already-encoded high InstanceID bits for direct low-level callers
|
|
const uint32_t userInstanceId = inst.descCpu.instanceID & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
const uint32_t instanceMaterialFlags =
|
|
(inst.descCpu.instanceID >> GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT) & GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
const uint32_t combinedMaterialFlags =
|
|
(meshMaterialFlags | instanceMaterialFlags) & GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
const uint32_t materialBits = combinedMaterialFlags << GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT;
|
|
outDesc->InstanceID = userInstanceId | materialBits;
|
|
outDesc->InstanceMask = (UINT8)(inst.descCpu.mask & 0xFFu);
|
|
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, mesh->materialFlags, 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);
|
|
|
|
const int uploadIndex = glRaytracingGetInactiveTLASIndex(world);
|
|
glRaytracingWaitFenceValue(world->instanceDescUploadFenceValue[uploadIndex]);
|
|
|
|
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<glRaytracingMeshRecord_t*> 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<glRaytracingMeshBuildInfo_t> 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;
|
|
const bool meshIsGlass = (mesh->materialFlags & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u;
|
|
info.geomDesc.Flags = (mesh->descCpu.opaque && !meshIsGlass)
|
|
? 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;
|
|
|
|
// Do not block the CPU here. The BLAS build was submitted before any TLAS
|
|
// build/lighting work that consumes it, and the shared D3D12 queue preserves
|
|
// that order. Command allocator reuse is protected by the ring fence in
|
|
// glRaytracingBeginBlasCmd().
|
|
for (size_t i = 0; i < builds.size(); ++i)
|
|
{
|
|
glRaytracingMeshRecord_t* mesh = builds[i].mesh;
|
|
mesh->currentBlasIndex = builds[i].newBlasIndex;
|
|
mesh->blasBuildFenceValue = blasFenceValue;
|
|
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;
|
|
|
|
const int buildTLASIndex = glRaytracingGetInactiveTLASIndex(world);
|
|
glRaytracingBuffer_t* dstTLAS = glRaytracingGetBuildTLASBuffer(world);
|
|
const glRaytracingBuffer_t* srcTLAS = glRaytracingGetCurrentTLASBufferConst(world);
|
|
|
|
// The TLAS build is queued after any pending BLAS builds on the same D3D12
|
|
// queue, so GPU ordering is sufficient and a CPU wait would stall the frame.
|
|
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;
|
|
|
|
// Keep TLAS builds asynchronous. Later ray dispatches are submitted to the
|
|
// same queue after this command list, so the GPU sees a complete TLAS before
|
|
// tracing without forcing the CPU to wait every update. The upload buffer is
|
|
// fence-tagged so the CPU does not overwrite instance descriptors still being
|
|
// consumed by an in-flight TLAS build.
|
|
world->instanceDescUploadFenceValue[buildTLASIndex] = tlasFenceValue;
|
|
world->currentTLASIndex = buildTLASIndex;
|
|
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<std::mutex> 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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearAllSceneStateInternal();
|
|
g_glRaytracingScene = glRaytracingSceneState_t();
|
|
glRaytracingShutdownCmdContext();
|
|
}
|
|
|
|
void glRaytracingClear(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearAllSceneStateInternal();
|
|
}
|
|
|
|
glRaytracingSceneHandle_t glRaytracingCreateScene(void)
|
|
{
|
|
std::lock_guard<std::mutex> 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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearWorldContents(world);
|
|
}
|
|
|
|
void glRaytracingDeleteScene(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingReleaseWorldResources(world);
|
|
*world = glRaytracingRenderWorld_t();
|
|
}
|
|
|
|
uint32_t glRaytracingGetSceneCount(void)
|
|
{
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
|
|
|
// Updating a mesh destroys/replaces resources that an already submitted frame
|
|
// may still reference. Wait only for this destructive path; steady-state
|
|
// rendering remains asynchronous.
|
|
glRaytracingWaitIdle();
|
|
|
|
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->blasBuildFenceValue = 0;
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 1;
|
|
mesh->currentBlasIndex = 0;
|
|
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 0);
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t glRaytracingGetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const glRaytracingMeshRecord_t* mesh = glRaytracingFindMeshConst(meshHandle);
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
return mesh->materialFlags;
|
|
}
|
|
|
|
void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint32_t materialFlags)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
materialFlags &= GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
if (mesh->materialFlags == materialFlags)
|
|
return;
|
|
|
|
mesh->materialFlags = materialFlags;
|
|
|
|
// The glass bit also represents shim-auto-tagged alpha-blended surfaces.
|
|
// It changes whether BLAS geometry is opaque, so force a full BLAS rebuild.
|
|
// The TLAS is also rebuilt so InstanceID carries the material bit.
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 1;
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 0);
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
}
|
|
|
|
void glRaytracingSetMeshGlass(glRaytracingMeshHandle_t meshHandle, int isGlass)
|
|
{
|
|
uint32_t flags = glRaytracingGetMeshMaterialFlags(meshHandle);
|
|
if (isGlass)
|
|
flags |= GL_RAYTRACING_MATERIAL_FLAG_GLASS;
|
|
else
|
|
flags &= ~GL_RAYTRACING_MATERIAL_FLAG_GLASS;
|
|
glRaytracingSetMeshMaterialFlags(meshHandle, flags);
|
|
}
|
|
|
|
void glRaytracingDeleteMesh(glRaytracingMeshHandle_t meshHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
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->blasBuildFenceValue = 0;
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 0;
|
|
|
|
if (meshHandle < g_glRaytracingScene.meshHandleToIndex.size())
|
|
g_glRaytracingScene.meshHandleToIndex[meshHandle] = -1;
|
|
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
}
|
|
|
|
static inline uint32_t glRaytracingNormalizeVisibleInstanceMask(uint32_t mask)
|
|
{
|
|
mask &= 0xFFu;
|
|
return mask ? mask : 0xFFu;
|
|
}
|
|
|
|
glRaytracingInstanceHandle_t glRaytracingCreateInstanceInScene(glRaytracingSceneHandle_t worldHandle, const glRaytracingInstanceDesc_t* desc)
|
|
{
|
|
std::lock_guard<std::mutex> 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.descCpu.mask = glRaytracingNormalizeVisibleInstanceMask(inst.descCpu.mask);
|
|
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<std::mutex> 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;
|
|
const uint32_t oldMask = ((uint32_t)inst->descCpu.mask) & 0xFFu;
|
|
const int wasHidden = (oldMask == 0u);
|
|
|
|
glRaytracingInstanceDesc_t newDesc = *desc;
|
|
|
|
// If this instance is hidden, keep it hidden even if the caller's
|
|
// transform-update helper sends mask = 0xFF again.
|
|
newDesc.mask = wasHidden
|
|
? 0u
|
|
: glRaytracingNormalizeVisibleInstanceMask(newDesc.mask);
|
|
|
|
inst->descCpu = newDesc;
|
|
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<std::mutex> 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<std::mutex> 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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
return glRaytracingBuildDirtyMeshesInternal();
|
|
}
|
|
|
|
int glRaytracingBuildSceneForHandle(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const glRaytracingRenderWorld_t* world = glRaytracingFindWorldConst(worldHandle);
|
|
return glRaytracingCountAliveInstances(world);
|
|
}
|
|
|
|
// ============================================================
|
|
// Lighting state
|
|
// ============================================================
|
|
|
|
struct glRaytracingLightingConstants_t
|
|
{
|
|
float invViewProj[16];
|
|
float invViewMatrix[16];
|
|
float viewProj[16];
|
|
float cameraPos[4];
|
|
float ambientColor[4];
|
|
float screenSize[4];
|
|
|
|
// Keep this CPU layout 16-byte aligned with the HLSL cbuffer.
|
|
float normalReconstructZ;
|
|
uint32_t lightCount;
|
|
uint32_t enableSpecular;
|
|
uint32_t enableHalfLambert;
|
|
|
|
float shadowBias;
|
|
uint32_t frameIndex;
|
|
uint32_t samplesPerPixel;
|
|
uint32_t maxBounces;
|
|
|
|
uint32_t enableDenoiser;
|
|
uint32_t denoisePassIndex;
|
|
float denoiseStepWidth;
|
|
float denoiseStrength;
|
|
|
|
float denoisePhiColor;
|
|
float denoisePhiNormal;
|
|
float denoisePhiPosition;
|
|
float bumpStrength;
|
|
};
|
|
|
|
struct glRaytracingLightingState_t
|
|
{
|
|
std::vector<glRaytracingLight_t> cpuLights;
|
|
// CPU-only metadata parallel to cpuLights. 0 = caller-managed light.
|
|
// Non-zero keys are used by the renderer bridge for persistent geometry emitters
|
|
// so they can be updated instead of cleared/re-added every frame.
|
|
std::vector<uint32_t> cpuLightKeys;
|
|
glRaytracingLightingConstants_t constants;
|
|
|
|
ComPtr<ID3D12DescriptorHeap> descriptorHeap;
|
|
ComPtr<ID3D12DescriptorHeap> descriptorHeapRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT descriptorStride;
|
|
|
|
glRaytracingBuffer_t constantBuffer;
|
|
glRaytracingBuffer_t lightBuffer;
|
|
void* constantBufferMapped;
|
|
void* lightBufferMapped;
|
|
glRaytracingBuffer_t constantBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
glRaytracingBuffer_t lightBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
void* constantBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
void* lightBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
|
|
ComPtr<ID3D12RootSignature> globalRootSig;
|
|
ComPtr<ID3D12RootSignature> localRootSig;
|
|
|
|
ComPtr<ID3D12StateObject> rtStateObject;
|
|
ComPtr<ID3D12StateObjectProperties> rtStateProps;
|
|
|
|
glRaytracingBuffer_t raygenTable;
|
|
glRaytracingBuffer_t missTable;
|
|
glRaytracingBuffer_t hitTable;
|
|
|
|
ComPtr<ID3D12PipelineState> denoisePSO;
|
|
ComPtr<ID3D12PipelineState> temporalPSO;
|
|
glRaytracingTexture_t pathTraceTexture;
|
|
glRaytracingTexture_t temporalTexture;
|
|
glRaytracingTexture_t historyTexture[2];
|
|
glRaytracingTexture_t denoiseTemp[2];
|
|
uint32_t currentHistoryIndex;
|
|
glRaytracingBuffer_t denoiseConstantBuffer[3];
|
|
glRaytracingBuffer_t denoiseConstantBufferRing[GL_RAYTRACING_CMD_RING_SIZE][3];
|
|
void* denoiseConstantBufferMapped[3];
|
|
void* denoiseConstantBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE][3];
|
|
UINT denoiseWidth;
|
|
UINT denoiseHeight;
|
|
DXGI_FORMAT denoiseFormat;
|
|
uint32_t frameCounter;
|
|
bool externalDenoiser;
|
|
ID3D12Resource* emissiveTexture;
|
|
DXGI_FORMAT emissiveFormat;
|
|
ID3D12Resource* specularTexture;
|
|
DXGI_FORMAT specularFormat;
|
|
bool uploadToCurrentFrameResource;
|
|
bool initialized;
|
|
|
|
glRaytracingLightingState_t()
|
|
{
|
|
memset(&constants, 0, sizeof(constants));
|
|
descriptorStride = 0;
|
|
constantBufferMapped = nullptr;
|
|
lightBufferMapped = nullptr;
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
constantBufferMappedRing[frame] = nullptr;
|
|
lightBufferMappedRing[frame] = nullptr;
|
|
for (int i = 0; i < 3; ++i)
|
|
denoiseConstantBufferMappedRing[frame][i] = nullptr;
|
|
}
|
|
for (int i = 0; i < 3; ++i)
|
|
denoiseConstantBufferMapped[i] = nullptr;
|
|
denoiseWidth = 0;
|
|
denoiseHeight = 0;
|
|
denoiseFormat = DXGI_FORMAT_UNKNOWN;
|
|
currentHistoryIndex = 0;
|
|
frameCounter = 0;
|
|
externalDenoiser = false;
|
|
emissiveTexture = nullptr;
|
|
emissiveFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
|
specularTexture = nullptr;
|
|
specularFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
uploadToCurrentFrameResource = false;
|
|
initialized = false;
|
|
}
|
|
};
|
|
|
|
static glRaytracingLightingState_t g_glRaytracingLighting;
|
|
|
|
static const DXGI_FORMAT GL_RAYTRACING_DENOISE_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
|
// CPU-only light metadata keys. These do not change the GPU Light layout.
|
|
// The high bit marks renderer-generated geometry emitters. They are kept across
|
|
// ordinary transient-light clears and upserted by key so moving the camera does
|
|
// not make an off-screen emissive card stop lighting the scene.
|
|
static const uint32_t GL_RAYTRACING_LIGHT_KEY_NONE = 0u;
|
|
static const uint32_t GL_RAYTRACING_LIGHT_KEY_GEOMETRY_EMISSIVE_BIT = 0x80000000u;
|
|
static const float GL_RAYTRACING_LIGHT_PERSISTANT_GEOMETRY_EMISSIVE = -2.0f;
|
|
|
|
static inline uint32_t glRaytracingMakeGeometryEmissiveLightKey(uint32_t key)
|
|
{
|
|
key &= ~GL_RAYTRACING_LIGHT_KEY_GEOMETRY_EMISSIVE_BIT;
|
|
if (key == GL_RAYTRACING_LIGHT_KEY_NONE)
|
|
key = 1u;
|
|
return key | GL_RAYTRACING_LIGHT_KEY_GEOMETRY_EMISSIVE_BIT;
|
|
}
|
|
|
|
static inline bool glRaytracingLightKeyIsGeometryEmissive(uint32_t key)
|
|
{
|
|
return (key & GL_RAYTRACING_LIGHT_KEY_GEOMETRY_EMISSIVE_BIT) != 0u;
|
|
}
|
|
|
|
static inline void glRaytracingLightingEnsureLightKeyCount()
|
|
{
|
|
if (g_glRaytracingLighting.cpuLightKeys.size() != g_glRaytracingLighting.cpuLights.size())
|
|
g_glRaytracingLighting.cpuLightKeys.resize(g_glRaytracingLighting.cpuLights.size(), GL_RAYTRACING_LIGHT_KEY_NONE);
|
|
}
|
|
|
|
enum glRaytracingLightingDescriptorIndex_t
|
|
{
|
|
GLR_DESC_LIGHTS_SRV = 0,
|
|
GLR_DESC_ALBEDO_SRV = 1,
|
|
GLR_DESC_DEPTH_SRV = 2,
|
|
GLR_DESC_NORMAL_SRV = 3,
|
|
GLR_DESC_POSITION_SRV = 4,
|
|
GLR_DESC_TLAS_SRV = 5,
|
|
GLR_DESC_PATHTRACE_SRV = 6,
|
|
GLR_DESC_DENOISE_A_SRV = 7,
|
|
GLR_DESC_DENOISE_B_SRV = 8,
|
|
GLR_DESC_HISTORY_SRV = 9,
|
|
GLR_DESC_TEMPORAL_SRV = 10,
|
|
GLR_DESC_EMISSIVE_SRV = 11,
|
|
GLR_DESC_SPECULAR_SRV = 12,
|
|
GLR_DESC_PATHTRACE_UAV = 13,
|
|
GLR_DESC_DENOISE_A_UAV = 14,
|
|
GLR_DESC_DENOISE_B_UAV = 15,
|
|
GLR_DESC_OUTPUT_UAV = 16,
|
|
GLR_DESC_TEMPORAL_UAV = 17,
|
|
GLR_DESC_HISTORY_UAV = 18,
|
|
GLR_DESC_COUNT = 19,
|
|
GLR_DESC_SRV_COUNT = 13,
|
|
GLR_DESC_UAV_COUNT = 6
|
|
};
|
|
|
|
static void glRaytracingLightingResetDenoiseHistory(void)
|
|
{
|
|
// Kept under the old name so existing call sites keep compiling. This now
|
|
// invalidates the temporal GI accumulator as well as restarting stochastic
|
|
// sample indexing after material/camera/light/scene changes.
|
|
g_glRaytracingLighting.frameCounter = 0;
|
|
g_glRaytracingLighting.constants.frameIndex = 0;
|
|
g_glRaytracingLighting.currentHistoryIndex = 0;
|
|
}
|
|
|
|
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;
|
|
|
|
// Reuses the old pad1 slot in glRaytracingLight_t. Keeping this in the
|
|
// same 16-byte lane preserves the CPU StructuredBuffer stride while giving
|
|
// point/spot lights an explicit volumetric scattering control.
|
|
// <= 0 disables the effect. Values around 0.25-1.0 are useful in Doom 3 units.
|
|
float volumetricScattering;
|
|
|
|
// 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
|
|
float falloffPower;
|
|
float3 falloffPad;
|
|
};
|
|
|
|
struct ShadowPayload
|
|
{
|
|
uint hit;
|
|
};
|
|
|
|
struct BouncePayload
|
|
{
|
|
uint hit;
|
|
float hitT;
|
|
uint materialFlags;
|
|
uint pad0;
|
|
};
|
|
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
};
|
|
|
|
StructuredBuffer<Light> gLights : register(t0);
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
RaytracingAccelerationStructure gSceneBVH : register(t5);
|
|
Texture2D<float4> gEmissiveTex : register(t11);
|
|
Texture2D<float4> gSpecularTex : register(t12);
|
|
RWTexture2D<float4> 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_NONE = 0;
|
|
static const uint GEOMETRY_FLAG_SKELETAL = 1;
|
|
static const uint GEOMETRY_FLAG_UNLIT = 2;
|
|
static const uint GEOMETRY_FLAG_GLASS = 4;
|
|
|
|
// The shim encodes per-instance material flags into the upper bits of
|
|
// D3D12_RAYTRACING_INSTANCE_DESC::InstanceID so any-hit shaders can make
|
|
// visibility decisions without binding a separate material table.
|
|
static const uint GL_RAYTRACING_INSTANCE_USER_ID_MASK = 0x0000FFFFu;
|
|
static const uint GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT = 16u;
|
|
static const uint GL_RAYTRACING_INSTANCE_MATERIAL_MASK = 0x000000FFu;
|
|
static const uint GL_RAYTRACING_MATERIAL_FLAG_GLASS = 0x00000001u;
|
|
|
|
float3 SafeNormalizeOr(float3 v, float3 fallbackDir);
|
|
|
|
uint DecodeInstanceMaterialFlags()
|
|
{
|
|
return (InstanceID() >> GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT) &
|
|
GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
}
|
|
|
|
bool CurrentRayHitIsGlass()
|
|
{
|
|
return (DecodeInstanceMaterialFlags() & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u;
|
|
}
|
|
|
|
uint DecodeGeometryFlag(float geoFlag)
|
|
{
|
|
// position.w comes from a render target / buffer path, so do not require exact
|
|
// float equality. Values like 0.999, 1.001, 1.99, 2.02 should decode correctly.
|
|
//
|
|
// Clamp negative garbage to 0, then round to nearest integer flag.
|
|
float f = max(geoFlag, 0.0);
|
|
return (uint)floor(f + 0.5);
|
|
}
|
|
|
|
bool GeometryFlagEquals(float geoFlag, uint expectedFlag)
|
|
{
|
|
return DecodeGeometryFlag(geoFlag) == expectedFlag;
|
|
}
|
|
|
|
bool GeometryFlagHas(float geoFlag, uint expectedFlag)
|
|
{
|
|
// Supports both current single-value usage and future bitmask usage.
|
|
uint decoded = DecodeGeometryFlag(geoFlag);
|
|
return (decoded & expectedFlag) != 0u;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
float SafeLengthSq(float3 v)
|
|
{
|
|
return max(dot(v, v), 1e-8);
|
|
}
|
|
|
|
float3 SafeNormalizeLocal(float3 v, float3 fallback)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallback;
|
|
}
|
|
|
|
float BumpLuminance(float3 c)
|
|
{
|
|
return dot(c, float3(0.299, 0.587, 0.114));
|
|
}
|
|
|
|
float3 EnhanceBumpNormal(uint2 pixel, float3 worldPos, float3 baseAlbedo, bool isSkeletal)
|
|
{
|
|
float3 N = SafeNormalizeLocal(LoadSceneNormal(pixel).xyz, float3(0.0, 0.0, 1.0));
|
|
|
|
// Character albedo has skin, cloth, and painted detail that should not be
|
|
// interpreted as height. The synthetic bump pass looks good on old walls,
|
|
// but it makes animated models crawl and self-shadow like noisy relief maps.
|
|
if (isSkeletal)
|
|
return N;
|
|
|
|
float strength = max(gBumpStrength, 0.0);
|
|
if (strength <= 0.001)
|
|
return N;
|
|
|
|
int2 p = int2(pixel);
|
|
int2 maxP = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
|
|
int2 pxm = clamp(p + int2(-1, 0), int2(0, 0), maxP);
|
|
int2 pxp = clamp(p + int2( 1, 0), int2(0, 0), maxP);
|
|
int2 pym = clamp(p + int2( 0, -1), int2(0, 0), maxP);
|
|
int2 pyp = clamp(p + int2( 0, 1), int2(0, 0), maxP);
|
|
|
|
float3 posL = gPositionTex.Load(int3(pxm, 0)).xyz;
|
|
float3 posR = gPositionTex.Load(int3(pxp, 0)).xyz;
|
|
float3 posU = gPositionTex.Load(int3(pym, 0)).xyz;
|
|
float3 posD = gPositionTex.Load(int3(pyp, 0)).xyz;
|
|
|
|
float3 T = posR - posL;
|
|
float3 B = posD - posU;
|
|
|
|
// Fall back to a stable tangent basis when the position buffer is flat or invalid.
|
|
if (dot(T, T) <= 1e-8 || dot(B, B) <= 1e-8)
|
|
{
|
|
float3 up = (abs(N.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0);
|
|
T = SafeNormalizeLocal(cross(up, N), float3(1.0, 0.0, 0.0));
|
|
B = cross(N, T);
|
|
}
|
|
else
|
|
{
|
|
T = SafeNormalizeLocal(T - N * dot(N, T), float3(1.0, 0.0, 0.0));
|
|
B = SafeNormalizeLocal(B - N * dot(N, B), float3(0.0, 1.0, 0.0));
|
|
}
|
|
|
|
float hL = BumpLuminance(saturate(gAlbedoTex.Load(int3(pxm, 0)).rgb));
|
|
float hR = BumpLuminance(saturate(gAlbedoTex.Load(int3(pxp, 0)).rgb));
|
|
float hU = BumpLuminance(saturate(gAlbedoTex.Load(int3(pym, 0)).rgb));
|
|
float hD = BumpLuminance(saturate(gAlbedoTex.Load(int3(pyp, 0)).rgb));
|
|
|
|
// Height-gradient bump from the diffuse texture. The scale is intentionally
|
|
// aggressive because the current renderer has no dedicated height/normal map
|
|
// slot here, and old idTech/Build textures need the fake relief to read.
|
|
float dhdx = (hR - hL);
|
|
float dhdy = (hD - hU);
|
|
float3 heightNormal = SafeNormalizeLocal(N - (T * dhdx + B * dhdy) * (strength * 4.25), N);
|
|
|
|
float3 nL = SafeNormalizeLocal(gNormalTex.Load(int3(pxm, 0)).xyz, N);
|
|
float3 nR = SafeNormalizeLocal(gNormalTex.Load(int3(pxp, 0)).xyz, N);
|
|
float3 nU = SafeNormalizeLocal(gNormalTex.Load(int3(pym, 0)).xyz, N);
|
|
float3 nD = SafeNormalizeLocal(gNormalTex.Load(int3(pyp, 0)).xyz, N);
|
|
float3 avgN = SafeNormalizeLocal((nL + nR + nU + nD) * 0.25, N);
|
|
|
|
// Amplify real G-buffer normal-map variation as well.
|
|
float3 detailN = SafeNormalizeLocal(N + (N - avgN) * (strength * 1.75), N);
|
|
|
|
float3 outN = SafeNormalizeLocal(lerp(detailN, heightNormal, 0.65), N);
|
|
|
|
// Avoid flipping normals so far that shadows/specular explode.
|
|
if (dot(outN, N) < 0.25)
|
|
outN = SafeNormalizeLocal(lerp(N, outN, 0.45), N);
|
|
|
|
return outN;
|
|
}
|
|
|
|
[shader("miss")]
|
|
void ShadowMiss(inout ShadowPayload payload)
|
|
{
|
|
payload.hit = 0;
|
|
}
|
|
|
|
[shader("anyhit")]
|
|
void ShadowAnyHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Glass/alpha-blended surfaces should participate in the primary/raster image,
|
|
// but visibility rays must continue through them. Mark the BLAS geometry
|
|
// non-opaque on the CPU side so this any-hit shader runs, then IgnoreHit()
|
|
// lets the ray keep going to whatever is behind the pane/sprite.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
IgnoreHit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
[shader("closesthit")]
|
|
void ShadowClosestHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Safety fallback for incorrectly-built glass geometry. Correct glass meshes
|
|
// are non-opaque and are ignored by ShadowAnyHit() above.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
payload.hit = 0;
|
|
return;
|
|
}
|
|
|
|
payload.hit = 1;
|
|
}
|
|
|
|
[shader("miss")]
|
|
void BounceMiss(inout BouncePayload payload)
|
|
{
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.pad0 = 0;
|
|
}
|
|
|
|
[shader("anyhit")]
|
|
void BounceAnyHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Secondary diffuse rays should see through the same transparent surfaces that
|
|
// shadow rays see through. This keeps a pane/sprite from killing all bounced
|
|
// light behind it.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
IgnoreHit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
[shader("closesthit")]
|
|
void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Fallback for transparent geometry that was accidentally built opaque.
|
|
// Correctly tagged transparent surfaces are ignored in BounceAnyHit() above.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.pad0 = 0;
|
|
return;
|
|
}
|
|
|
|
payload.hit = 1;
|
|
payload.hitT = RayTCurrent();
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.pad0 = 0;
|
|
}
|
|
|
|
[shader("closesthit")]
|
|
void ReflectionClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Specular rays are view rays, not diffuse/visibility rays. They should
|
|
// report the first reflected surface even when that surface was tagged as
|
|
// glass/alpha, otherwise glass panes/sprites vanish from mirror-like hits.
|
|
payload.hit = 1;
|
|
payload.hitT = RayTCurrent();
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.pad0 = 0;
|
|
}
|
|
|
|
float TraceShadow(float3 origin, float3 dir, float maxT)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 5.0;
|
|
ray.TMax = maxT;
|
|
|
|
ShadowPayload payload;
|
|
payload.hit = 0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH,
|
|
0xFF,
|
|
0,
|
|
0,
|
|
0,
|
|
ray,
|
|
payload);
|
|
|
|
return (payload.hit != 0) ? 0.0 : 1.0;
|
|
}
|
|
|
|
bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 0.001;
|
|
ray.TMax = maxT;
|
|
|
|
BouncePayload payload;
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.pad0 = 0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH,
|
|
0xFF,
|
|
1,
|
|
0,
|
|
1,
|
|
ray,
|
|
payload);
|
|
|
|
hitT = payload.hitT;
|
|
materialFlags = payload.materialFlags;
|
|
return payload.hit != 0;
|
|
}
|
|
|
|
bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 0.001;
|
|
ray.TMax = maxT;
|
|
|
|
BouncePayload payload;
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.pad0 = 0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_NONE,
|
|
0xFF,
|
|
2,
|
|
0,
|
|
1,
|
|
ray,
|
|
payload);
|
|
|
|
hitT = payload.hitT;
|
|
materialFlags = payload.materialFlags;
|
|
return payload.hit != 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);
|
|
}
|
|
|
|
float3 Doom3SafeNormalizeOr(float3 v, float3 fallbackDir)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir;
|
|
}
|
|
|
|
float Doom3QuadraticFalloffImage(float texCoord)
|
|
{
|
|
// Math version of Doom 3 BFG's built-in _quadratic lookup table.
|
|
// The source table is 32 texels wide, brightest at the center and clamped
|
|
// to black outside the light volume.
|
|
if (texCoord <= 0.0 || texCoord >= 1.0)
|
|
return 0.0;
|
|
|
|
const float QUADRATIC_WIDTH = 32.0;
|
|
|
|
// Convert a normalized lookup coordinate to the source generator's texel-space
|
|
// x value, then apply the same centered squared ramp used by R_QuadraticImage().
|
|
float x = texCoord * QUADRATIC_WIDTH - 0.5;
|
|
float d = x - (QUADRATIC_WIDTH * 0.5 - 0.5);
|
|
d = abs(d);
|
|
d -= 0.5;
|
|
d /= (QUADRATIC_WIDTH * 0.5);
|
|
d = 1.0 - d;
|
|
d = saturate(d);
|
|
return d * d;
|
|
}
|
|
|
|
float Doom3QuadraticCentered(float centeredCoord)
|
|
{
|
|
// centeredCoord is -1 at one side of the light volume, 0 at the light center,
|
|
// and +1 at the opposite side.
|
|
return Doom3QuadraticFalloffImage(centeredCoord * 0.5 + 0.5);
|
|
}
|
|
|
|
float Doom3ProjectionTexture2D(float2 centeredCoord)
|
|
{
|
|
// Doom 3 multiplies a projected light image by a separate falloff image. This
|
|
// renderer does not bind Doom light materials/cookies, so use the same built-in
|
|
// quadratic shape on S/T as a neutral default projection texture approximation.
|
|
if (abs(centeredCoord.x) >= 1.0 || abs(centeredCoord.y) >= 1.0)
|
|
return 0.0;
|
|
|
|
return Doom3QuadraticCentered(centeredCoord.x) * Doom3QuadraticCentered(centeredCoord.y);
|
|
}
|
|
|
|
float Doom3ProjectedCookieDefault(float2 centeredCoord)
|
|
{
|
|
// Without the actual light stage image, keep default projected lights neutral
|
|
// inside their S/T clip planes. The default Doom 3 projected falloff is
|
|
// _noFalloff, and using the point-light quadratic here makes spotlights much
|
|
// darker than the engine's fixed-function interaction path.
|
|
return (abs(centeredCoord.x) < 1.0 && abs(centeredCoord.y) < 1.0) ? 1.0 : 0.0;
|
|
}
|
|
)"
|
|
R"(
|
|
float Doom3ProjectedDepthFalloff(float depth, float nearClip, float farClip)
|
|
{
|
|
// Doom 3's default projected light uses _noFalloff: it is clipped by the
|
|
// start/end planes but does not distance-fade through the frustum.
|
|
return (depth > nearClip && depth < farClip) ? 1.0 : 0.0;
|
|
}
|
|
|
|
float AttenuationBasic(float lightDist, float maxLightDist, float falloffPower)
|
|
{
|
|
float attenuation = 1.0 - saturate(lightDist / max(maxLightDist, 1e-4));
|
|
falloffPower = (falloffPower > 0.0) ? falloffPower : 0.25;
|
|
return pow(attenuation, falloffPower);
|
|
}
|
|
|
|
float Spotify(float3 lightVector, float3 spotDir, float outerAngle, float innerAngle)
|
|
{
|
|
return smoothstep(outerAngle, innerAngle, saturate(dot(lightVector, spotDir)));
|
|
}
|
|
|
|
float ComputePointLightAttenuation(float3 worldPos, Light Lgt)
|
|
{
|
|
float3 radii = GetPointLightRadius(Lgt);
|
|
float3 offset = worldPos - Lgt.position;
|
|
|
|
// Rage/Tango-style point attenuation from the provided shader:
|
|
// perAxis = 1 - saturate(abs(fragment - lightPos) / extents)
|
|
// attenuation = pow(perAxis.x * perAxis.y * perAxis.z, falloffPower)
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
float3 axisW = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
|
|
float3 perAxis;
|
|
perAxis.x = 1.0 - saturate(abs(dot(offset, axisU)) / radii.x);
|
|
perAxis.y = 1.0 - saturate(abs(dot(offset, axisV)) / radii.y);
|
|
perAxis.z = 1.0 - saturate(abs(dot(offset, axisW)) / radii.z);
|
|
|
|
float falloffPower = (Lgt.falloffPower > 0.0) ? Lgt.falloffPower : 0.25;
|
|
return pow(perAxis.x * perAxis.y * perAxis.z, falloffPower);
|
|
}
|
|
)"
|
|
R"(
|
|
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);
|
|
|
|
float3 spotDir = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
|
|
float depth = dot(lightToSurface, spotDir);
|
|
|
|
if (depth <= nearClip || depth >= farClip)
|
|
return 0.0;
|
|
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
|
|
float invDepth = 1.0 / max(depth, 1e-4);
|
|
|
|
float halfU = max(abs(Lgt.halfWidth), 1e-4);
|
|
float halfV = max(abs(Lgt.halfHeight), 1e-4);
|
|
|
|
float signedU = (dot(lightToSurface, axisU) * invDepth) / halfU;
|
|
float signedV = (dot(lightToSurface, axisV) * invDepth) / halfV;
|
|
|
|
if (abs(signedU) >= 1.0 || abs(signedV) >= 1.0)
|
|
return 0.0;
|
|
|
|
float3 lightVector = normalize(lightToSurface);
|
|
float maxSlope = max(halfU, halfV);
|
|
float outerAngle = rsqrt(1.0 + maxSlope * maxSlope);
|
|
float innerSlope = maxSlope * 0.65;
|
|
float innerAngle = rsqrt(1.0 + innerSlope * innerSlope);
|
|
|
|
float spotCookie = Spotify(lightVector, spotDir, outerAngle, innerAngle);
|
|
float distFalloff = AttenuationBasic(depth - nearClip, farClip - nearClip, Lgt.falloffPower);
|
|
|
|
return spotCookie * distFalloff;
|
|
}
|
|
|
|
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 = 4;
|
|
|
|
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;
|
|
}
|
|
)"
|
|
R"(
|
|
static const uint QD3D12_MAX_RECT_LIGHT_SAMPLES = 4u;
|
|
|
|
uint ClampRectLightSampleCount(uint requestedSamples)
|
|
{
|
|
return min(max(requestedSamples, 1u), QD3D12_MAX_RECT_LIGHT_SAMPLES);
|
|
}
|
|
|
|
float2 StableRectLightSampleUV(uint sampleIndex, uint sampleCount)
|
|
{
|
|
if (sampleCount <= 1u)
|
|
return float2(0.0, 0.0);
|
|
|
|
// Deterministic, symmetric positions. Do not rotate/jitter these per pixel
|
|
// or per frame; changing the sample pattern was the visible sparkle source
|
|
// for geometry emitter cards. Larger emitters can request up to 16 stable
|
|
// taps, which is still cheap enough for a small number of tagged emitters.
|
|
uint s = sampleIndex & 15u;
|
|
|
|
if (sampleCount <= 4u)
|
|
{
|
|
// Center-first pattern: fast, stable, and less likely to make a large
|
|
// visible emitter go black just because every corner tap was occluded.
|
|
if (s == 0u) return float2( 0.00, 0.00);
|
|
if (s == 1u) return float2(-0.60, -0.60);
|
|
if (s == 2u) return float2( 0.60, -0.60);
|
|
return float2( 0.00, 0.60);
|
|
}
|
|
|
|
if (sampleCount <= 8u)
|
|
{
|
|
if (s == 0u) return float2( 0.00, 0.00);
|
|
if (s == 1u) return float2(-0.65, -0.65);
|
|
if (s == 2u) return float2( 0.65, -0.65);
|
|
if (s == 3u) return float2(-0.65, 0.65);
|
|
if (s == 4u) return float2( 0.65, 0.65);
|
|
if (s == 5u) return float2(-0.25, 0.00);
|
|
if (s == 6u) return float2( 0.25, 0.00);
|
|
return float2( 0.00, 0.25);
|
|
}
|
|
|
|
// 4x4 stratified grid in [-0.875, +0.875].
|
|
float x = (((float)(s & 3u) + 0.5) * 0.5) - 1.0;
|
|
float y = (((float)(s >> 2u) + 0.5) * 0.5) - 1.0;
|
|
return float2(x, y) * 0.875;
|
|
}
|
|
|
|
float RectLightShadow(float3 worldPos, float3 N, Light Lgt, uint2 pixel)
|
|
{
|
|
uint sampleCount = ClampRectLightSampleCount(Lgt.samples);
|
|
|
|
float visibility = 0.0;
|
|
|
|
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 uv = StableRectLightSampleUV(s, sampleCount);
|
|
|
|
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, bool isSkeletal)
|
|
{
|
|
const uint AO_SAMPLES = 8;
|
|
float aoRadius = isSkeletal ? 18.0 : 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 * (isSkeletal ? 0.75 : 0.15));
|
|
|
|
visibility += TraceShadow(aoOrigin, aoDir, aoRadius);
|
|
}
|
|
|
|
visibility /= (float)AO_SAMPLES;
|
|
visibility = saturate(pow(visibility, 1.5));
|
|
|
|
if (isSkeletal)
|
|
visibility = lerp(visibility, 1.0, 0.45);
|
|
|
|
return visibility;
|
|
}
|
|
|
|
float3 GetSkyLightDirection10AM()
|
|
{
|
|
// Direction from the shaded point TO the sky/sun.
|
|
// 10:00 AM style: angled, not straight vertical.
|
|
//
|
|
// Flip X/Y signs if you want the shadows cast the opposite horizontal way.
|
|
return normalize(float3(-0.55, -0.25, 0.80));
|
|
}
|
|
)"
|
|
R"(
|
|
float ComputeSkyVisibility(float3 worldPos, float3 N, uint2 pixel)
|
|
{
|
|
const uint SKY_SAMPLES = 4;
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
// Soft angular size of the sky/sun shadow cone.
|
|
// Larger = softer shadows, but more chance of light leaking.
|
|
const float SKY_SOFTNESS = 0.085;
|
|
|
|
float3 skyCenterDir = GetSkyLightDirection10AM();
|
|
|
|
float NoSky = dot(N, skyCenterDir);
|
|
|
|
// Mostly back-facing relative to the sky direction.
|
|
// Return black visibility instead of casting unstable grazing rays.
|
|
if (NoSky <= -0.35)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(skyCenterDir, tangent, bitangent);
|
|
|
|
float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 1.0, saturate(NoSky));
|
|
|
|
float3 baseOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyCenterDir * (gShadowBias * 2.0);
|
|
|
|
float visibility = 0.0;
|
|
|
|
// IMPORTANT:
|
|
// No per-pixel random rotation here.
|
|
// The old noise came from random hemisphere sky sampling.
|
|
// This keeps the soft shadow sampling pattern stable per pixel/frame.
|
|
[unroll]
|
|
for (uint i = 0; i < SKY_SAMPLES; ++i)
|
|
{
|
|
float2 xi = Hammersley2D(i, SKY_SAMPLES, 0.0);
|
|
float2 d = ConcentricSampleDisk(xi) * SKY_SOFTNESS;
|
|
|
|
float3 skyDir = normalize(
|
|
skyCenterDir +
|
|
tangent * d.x +
|
|
bitangent * d.y);
|
|
|
|
// Do not shoot rays below the world horizon.
|
|
if (skyDir.z <= 0.02)
|
|
{
|
|
visibility += 0.0;
|
|
continue;
|
|
}
|
|
|
|
float sampleFacing = dot(N, skyDir);
|
|
|
|
// Avoid very noisy grazing rays on back-facing surfaces.
|
|
if (sampleFacing <= -0.35)
|
|
{
|
|
visibility += 0.0;
|
|
continue;
|
|
}
|
|
|
|
float3 skyOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyDir * (gShadowBias * 2.0);
|
|
|
|
visibility += TraceShadow(skyOrigin, skyDir, SKY_TMAX);
|
|
}
|
|
|
|
visibility /= (float)SKY_SAMPLES;
|
|
|
|
// Slightly smooth the binary ray result so it does not look harsh.
|
|
return saturate(visibility);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
float Doom3SpecularLookup(float x)
|
|
{
|
|
// Doom 3 used a lookup table for specular falloff. A single high-power
|
|
// lobe is too binary with this G-buffer path: small normal-map/grazing
|
|
// differences make some materials lose specular completely. Use a broad
|
|
// plastic lobe plus a tighter hot spot so highlights stay readable without
|
|
// turning into a flat additive wash.
|
|
x = saturate(x);
|
|
|
|
float broad = pow(x, 12.0);
|
|
float tight = pow(x, 48.0);
|
|
|
|
return saturate(broad * 0.55 + tight * 0.85);
|
|
}
|
|
|
|
float SpecularPeak3(float3 c)
|
|
{
|
|
return max(max(c.r, c.g), c.b);
|
|
}
|
|
|
|
bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo)
|
|
{
|
|
// Normal path: alpha is the raster G-buffer validity bit.
|
|
if (specSample.a > 0.5)
|
|
return true;
|
|
|
|
// Tolerant path: some raster paths/specular inputs write RGB but leave the
|
|
// validity alpha at zero. Do not treat the fallback albedo descriptor as a
|
|
// spec map; when no specular texture is bound, specSample.rgb == baseAlbedo.
|
|
float3 specRgb = saturate(specSample.rgb);
|
|
float3 baseRgb = saturate(baseAlbedo);
|
|
float rgbPeak = SpecularPeak3(specRgb);
|
|
float rgbDiff = length(specRgb - baseRgb);
|
|
|
|
return rgbPeak > 0.025 && rgbDiff > 0.035;
|
|
}
|
|
|
|
float3 Doom3PseudoSpecularMask(float3 baseAlbedo)
|
|
{
|
|
// Doom 3 normally uses a dedicated specular map.
|
|
// This fallback is only used for pixels whose specular G-buffer says no
|
|
// specular map was written by the raster pass.
|
|
float lum = dot(saturate(baseAlbedo), float3(0.299, 0.587, 0.114));
|
|
|
|
float specStrength = lerp(0.22, 0.72, saturate(lum * 1.35));
|
|
|
|
// Slight warm/colored contribution from the diffuse texture, but mostly neutral
|
|
// like a missing/default specular map.
|
|
float3 neutralSpec = float3(specStrength, specStrength, specStrength);
|
|
float3 tintedSpec = saturate(baseAlbedo) * 0.35 + neutralSpec * 0.65;
|
|
|
|
return max(tintedSpec, float3(0.18, 0.18, 0.18));
|
|
}
|
|
|
|
float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo)
|
|
{
|
|
float4 specSample = gSpecularTex.Load(int3(pixel, 0));
|
|
|
|
// The raster G-buffer writer stores alpha as a validity bit. This matters
|
|
// for black specular maps: black should mean zero specular, not "missing map".
|
|
// Also accept RGB-only specular inputs when they are clearly not the fallback
|
|
// albedo descriptor, which fixes materials whose specular buffer forgot to
|
|
// set the alpha-valid bit.
|
|
if (LooksLikeAuthoredSpecularSample(specSample, baseAlbedo))
|
|
return saturate(specSample.rgb);
|
|
|
|
return Doom3PseudoSpecularMask(baseAlbedo);
|
|
}
|
|
|
|
|
|
float ComputeDiffuseLightingTerm(float3 N, float3 L)
|
|
{
|
|
// Keep the original light volume/range attenuation exactly where it is, but
|
|
// make the surface response less gamey. The old 0.28-0.32 Half-Lambert wrap
|
|
// pushed too much light around silhouettes and into back-facing normal-map
|
|
// detail. This smaller squared wrap keeps Doom/idTech readability while
|
|
// giving a more Lambert-like, physically plausible rolloff.
|
|
float rawNoL = dot(normalize(N), normalize(L));
|
|
|
|
if (gEnableHalfLambert != 0u)
|
|
{
|
|
const float REALISTIC_WRAP = 0.12;
|
|
float wrapped = saturate((rawNoL + REALISTIC_WRAP) / (1.0 + REALISTIC_WRAP));
|
|
return wrapped * wrapped;
|
|
}
|
|
|
|
return saturate(rawNoL);
|
|
}
|
|
|
|
float EstimateSpecularRoughness(float3 specularAlbedo)
|
|
{
|
|
// No roughness map is available in this G-buffer path, so infer a stable
|
|
// perceptual roughness from the specular map strength. Brighter spec maps
|
|
// get tighter highlights; dark/missing maps stay broad and subdued.
|
|
float peak = SpecularPeak3(saturate(specularAlbedo));
|
|
return clamp(lerp(0.68, 0.34, peak), 0.28, 0.72);
|
|
}
|
|
|
|
float3 ComputeSpecular(
|
|
float3 N,
|
|
float3 V,
|
|
float3 L,
|
|
float3 lightColor,
|
|
float lightIntensity,
|
|
float atten,
|
|
float shadow,
|
|
float3 specularAlbedo)
|
|
{
|
|
if (gEnableSpecular == 0)
|
|
return 0.0;
|
|
|
|
if (atten <= 0.0 || shadow <= 0.0)
|
|
return 0.0;
|
|
|
|
N = normalize(N);
|
|
V = normalize(V);
|
|
L = normalize(L);
|
|
|
|
float NoL = saturate(dot(N, L));
|
|
float NoV = saturate(dot(N, V));
|
|
|
|
if (NoL <= 1.0e-4 || NoV <= 1.0e-4)
|
|
return 0.0;
|
|
|
|
float3 H = Doom3SafeNormalizeOr(L + V, N);
|
|
float NoH = saturate(dot(N, H));
|
|
float VoH = saturate(dot(V, H));
|
|
|
|
if (NoH <= 1.0e-4 || VoH <= 1.0e-4)
|
|
return 0.0;
|
|
|
|
float3 specMask = saturate(specularAlbedo);
|
|
float specPeak = SpecularPeak3(specMask);
|
|
if (specPeak <= 0.001)
|
|
return 0.0;
|
|
|
|
float roughness = EstimateSpecularRoughness(specMask);
|
|
float a = roughness * roughness;
|
|
float a2 = max(a * a, 1.0e-4);
|
|
|
|
const float PI = 3.14159265;
|
|
|
|
// GGX/Trowbridge-Reitz distribution with Smith masking and Schlick Fresnel.
|
|
// This is still intentionally stylized for the existing idTech-style assets,
|
|
// but it produces more believable view-dependent highlights than the previous
|
|
// additive lookup lobe and does not alter light attenuation distance.
|
|
float dDenom = NoH * NoH * (a2 - 1.0) + 1.0;
|
|
float D = a2 / max(PI * dDenom * dDenom, 1.0e-4);
|
|
|
|
float k = ((roughness + 1.0) * (roughness + 1.0)) * 0.125;
|
|
float Gv = NoV / max(NoV * (1.0 - k) + k, 1.0e-4);
|
|
float Gl = NoL / max(NoL * (1.0 - k) + k, 1.0e-4);
|
|
float G = Gv * Gl;
|
|
|
|
float3 F0 = saturate(lerp(float3(0.025, 0.025, 0.025), specMask, 0.58));
|
|
float3 F = F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);
|
|
|
|
float specTerm = (D * G) / max(4.0 * NoL * NoV, 1.0e-4);
|
|
specTerm = min(specTerm, 5.0);
|
|
|
|
const float SPECULAR_ENERGY_SCALE = 2.35;
|
|
|
|
float3 specular =
|
|
lightColor *
|
|
lightIntensity *
|
|
atten *
|
|
shadow *
|
|
NoL *
|
|
F *
|
|
specTerm *
|
|
SPECULAR_ENERGY_SCALE;
|
|
|
|
return clamp(specular, 0.0, 8.0);
|
|
}
|
|
)"
|
|
R"(
|
|
float TraceStraightUpToSky(float3 worldPos, float3 N)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
float3 skyDir = float3(0.0, 0.0, 1.0);
|
|
float NoSky = dot(N, skyDir);
|
|
float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 1.0, saturate(NoSky));
|
|
|
|
float3 skyOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyDir * (gShadowBias * 2.0);
|
|
|
|
return TraceShadow(skyOrigin, skyDir, SKY_TMAX);
|
|
}
|
|
|
|
uint PcgHash(uint input)
|
|
{
|
|
uint state = input * 747796405u + 2891336453u;
|
|
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
return (word >> 22u) ^ word;
|
|
}
|
|
|
|
uint InitRng(uint2 pixel, uint frameIndex, uint sampleIndex)
|
|
{
|
|
uint seed = pixel.x * 1973u;
|
|
seed ^= pixel.y * 9277u;
|
|
seed ^= frameIndex * 26699u;
|
|
seed ^= sampleIndex * 374761393u;
|
|
return PcgHash(seed) | 1u;
|
|
}
|
|
|
|
float Rand(inout uint rng)
|
|
{
|
|
rng = PcgHash(rng);
|
|
return (float)rng * 2.3283064365386963e-10;
|
|
}
|
|
|
|
float2 Rand2(inout uint rng)
|
|
{
|
|
return float2(Rand(rng), Rand(rng));
|
|
}
|
|
|
|
float2 RayReconstructionTemporalDisk(uint2 pixel, uint sampleIndex, float radius)
|
|
{
|
|
uint rng = InitRng(pixel, gFrameIndex, sampleIndex);
|
|
return ConcentricSampleDisk(Rand2(rng)) * radius;
|
|
}
|
|
|
|
float3 SampleCosineWorld(float3 N, inout uint rng)
|
|
{
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(N, tangent, bitangent);
|
|
|
|
float3 localDir = CosineSampleHemisphere(Rand2(rng));
|
|
return normalize(
|
|
tangent * localDir.x +
|
|
bitangent * localDir.y +
|
|
N * localDir.z);
|
|
}
|
|
|
|
float3 SampleConeWorld(float3 centerDir, float coneRadius, inout uint rng)
|
|
{
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(centerDir, tangent, bitangent);
|
|
|
|
float2 d = ConcentricSampleDisk(Rand2(rng)) * coneRadius;
|
|
return normalize(centerDir + tangent * d.x + bitangent * d.y);
|
|
}
|
|
|
|
float3 GetSkyRadiance(float3 dir)
|
|
{
|
|
float upness = saturate(dir.z * 0.5 + 0.5);
|
|
|
|
float3 warmSky = float3(0.98, 0.55, 0.35);
|
|
float3 coolSky = float3(0.30, 0.40, 0.62);
|
|
float3 sky = lerp(warmSky * 0.22, coolSky * 0.55, upness);
|
|
|
|
float sunAmount = pow(saturate(dot(dir, GetSkyLightDirection10AM())), 96.0);
|
|
sky += warmSky * (sunAmount * 2.25);
|
|
|
|
return sky;
|
|
}
|
|
|
|
float TraceVisibilityBiased(float3 worldPos, float3 N, float3 dir, float maxT)
|
|
{
|
|
float NoD = saturate(dot(N, dir));
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD);
|
|
float3 origin = worldPos + N * normalBias + dir * (gShadowBias * 0.5);
|
|
return TraceShadow(origin, dir, max(maxT - gShadowBias * 0.5, 0.001));
|
|
}
|
|
|
|
|
|
float3 CompressEmissiveRadiance(float3 e, float peakLimit)
|
|
{
|
|
e = max(e, 0.0);
|
|
float peak = max(max(e.r, e.g), e.b);
|
|
if (peak > peakLimit && peak > 1e-5)
|
|
e *= peakLimit / peak;
|
|
return e;
|
|
}
|
|
|
|
float3 LoadEmissiveRadianceClamped(int2 p)
|
|
{
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
p = clamp(p, int2(0, 0), maxPixel);
|
|
float3 radiance = CompressEmissiveRadiance(gEmissiveTex.Load(int3(p, 0)).rgb, 18.0);
|
|
return min(radiance + sqrt(max(radiance, 0.0)) * 0.35, 22.0);
|
|
}
|
|
|
|
float3 EstimateEmissiveBloomAtPixel(uint2 pixel)
|
|
{
|
|
int2 p = int2(pixel);
|
|
|
|
// Strong threshold-free bloom in the ray output. Particle emissive lighting
|
|
// is injected separately below; this is the visible camera halo.
|
|
float3 bloom = 0.0;
|
|
bloom += LoadEmissiveRadianceClamped(p) * 0.320;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 1, 0)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-1, 0)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 1)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -1)) * 0.420;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 2, 2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-2, 2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 2, -2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-2, -2)) * 0.260;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 4, 0)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-4, 0)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 4)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -4)) * 0.180;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 8, 0)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-8, 0)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 8)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -8)) * 0.120;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 14, 0)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-14, 0)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 14)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -14)) * 0.075;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 24, 0)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-24, 0)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 24)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -24)) * 0.045;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 42, 0)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-42, 0)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 42)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -42)) * 0.025;
|
|
|
|
return min(bloom * 1.45, 28.0);
|
|
}
|
|
|
|
float3 SafeNormalizeOr(float3 v, float3 fallback)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
if (lenSq <= 1e-8)
|
|
return fallback;
|
|
return v * rsqrt(lenSq);
|
|
}
|
|
|
|
bool TryScreenParticleWorldCandidate(float4 clip, float3 receiverPos, inout float bestDistSq, inout float3 bestWorld)
|
|
{
|
|
float4 worldA = mul(clip, gInvViewProj);
|
|
if (abs(worldA.w) > 1e-6)
|
|
{
|
|
float3 candidate = worldA.xyz / worldA.w;
|
|
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
|
|
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
|
|
{
|
|
bestDistSq = distSq;
|
|
bestWorld = candidate;
|
|
}
|
|
}
|
|
|
|
float4 worldB = mul(gInvViewProj, clip);
|
|
if (abs(worldB.w) > 1e-6)
|
|
{
|
|
float3 candidate = worldB.xyz / worldB.w;
|
|
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
|
|
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
|
|
{
|
|
bestDistSq = distSq;
|
|
bestWorld = candidate;
|
|
}
|
|
}
|
|
|
|
return bestDistSq < 147456.0;
|
|
}
|
|
|
|
bool TryReconstructScreenParticleWorld(uint2 particlePixel, float particleDepth, float3 receiverPos, out float3 particleWorld)
|
|
{
|
|
float2 uv = ((float2)particlePixel + 0.5) / max(gScreenSize.xy, float2(1.0, 1.0));
|
|
float x = uv.x * 2.0 - 1.0;
|
|
float yUp = 1.0 - uv.y * 2.0;
|
|
float yDown = uv.y * 2.0 - 1.0;
|
|
|
|
float bestDistSq = 1.0e30;
|
|
particleWorld = receiverPos;
|
|
TryScreenParticleWorldCandidate(float4(x, yUp, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
|
|
TryScreenParticleWorldCandidate(float4(x, yDown, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
|
|
return bestDistSq < 147456.0;
|
|
}
|
|
|
|
float3 EstimateScreenSpaceEmissiveParticleLighting(uint2 pixel, float3 worldPos, float3 N)
|
|
{
|
|
static const int2 kParticleLightTaps[17] =
|
|
{
|
|
int2( 0, 0),
|
|
int2( 7, 0), int2( -7, 0), int2( 0, 7), int2( 0, -7),
|
|
int2( 10, 10), int2(-10, 10), int2( 10, -10), int2(-10, -10),
|
|
int2( 18, 4), int2(-18, -4), int2( 4, 18), int2( -4, -18),
|
|
int2( 30, 0), int2(-30, 0), int2( 0, 30), int2( 0, -30)
|
|
};
|
|
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
float pixelScale = max(1.0, round(min(gScreenSize.x, gScreenSize.y) / 720.0));
|
|
float3 lighting = 0.0;
|
|
|
|
[unroll]
|
|
for (uint i = 0u; i < 17u; ++i)
|
|
{
|
|
int2 sp = int2(pixel) + int2(round((float2)kParticleLightTaps[i] * pixelScale));
|
|
sp = clamp(sp, int2(0, 0), maxPixel);
|
|
|
|
float4 particleEmission = gEmissiveTex.Load(int3(sp, 0));
|
|
if (particleEmission.a <= 1.5)
|
|
continue;
|
|
|
|
float particleDepth = saturate(particleEmission.a - 2.0);
|
|
float3 particleWorld;
|
|
if (!TryReconstructScreenParticleWorld(uint2(sp), particleDepth, worldPos, particleWorld))
|
|
continue;
|
|
|
|
float3 toParticle = particleWorld - worldPos;
|
|
float distSq = dot(toParticle, toParticle);
|
|
if (distSq <= 1e-4 || distSq >= 147456.0)
|
|
continue;
|
|
|
|
float dist = sqrt(distSq);
|
|
float3 L = toParticle / dist;
|
|
float NoL = saturate(dot(N, L));
|
|
if (NoL <= 0.01)
|
|
continue;
|
|
|
|
float visibility = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
if (visibility <= 0.0)
|
|
continue;
|
|
|
|
float distanceFade = saturate(1.0 - dist / 640.0);
|
|
distanceFade *= distanceFade;
|
|
float attenuation = distanceFade / (1.0 + distSq * 0.000075);
|
|
lighting += CompressEmissiveRadiance(particleEmission.rgb, 24.0) * (NoL * visibility * attenuation);
|
|
}
|
|
|
|
return lighting * 9.0;
|
|
}
|
|
|
|
// Screen-space material projection removed. Secondary/reflection paths no longer
|
|
// project DXR hits back into the camera G-buffer.
|
|
float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
uint bounceSamples = max(gMaxBounces, 1u);
|
|
bounceSamples = min(bounceSamples, 4u);
|
|
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint b = 0; b < bounceSamples; ++b)
|
|
{
|
|
float3 dir = SampleCosineWorld(N, rng);
|
|
float NoD = saturate(dot(N, dir));
|
|
float visibility = TraceVisibilityBiased(worldPos, N, dir, SKY_TMAX);
|
|
|
|
// This is a G-buffer path-traced approximation: secondary hits are used as
|
|
// occluders because this pass does not bind per-triangle material data yet.
|
|
accum += GetSkyRadiance(dir) * visibility * NoD;
|
|
}
|
|
|
|
accum /= (float)bounceSamples;
|
|
return accum * 0.55;
|
|
}
|
|
|
|
float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
|
|
{
|
|
specularOut = 0.0;
|
|
|
|
float3 toCenter = Lgt.position - worldPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 centerDir = toCenter / centerDist;
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(centerDir, tangent, bitangent);
|
|
|
|
float atten = ComputePointLightAttenuation(worldPos, Lgt);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
uint sampleCount = max(Lgt.samples, 1u);
|
|
sampleCount = (gEnableDenoiser == 0u) ? 1u : min(sampleCount, 4u);
|
|
|
|
// One random area-light sample per frame was one of the visible noise sources.
|
|
// Use a deterministic low-discrepancy pattern instead. With a single sample,
|
|
// use the light center so default point lights are hard-shadowed and stable.
|
|
float areaRadius = (sampleCount > 1u && Lgt.samples > 1u) ? max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12) : 0.0;
|
|
if (gEnableDenoiser == 0u && Lgt.samples != 0u)
|
|
areaRadius = max(areaRadius, min(max(GetPointLightMaxRadius(Lgt) * 0.006, 0.04), 1.25));
|
|
float rand = Hash12((float2)pixel + worldPos.xy + float2(worldPos.z, centerDist));
|
|
|
|
float3 diffuseAccum = 0.0;
|
|
float3 specAccum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < sampleCount; ++s)
|
|
{
|
|
float2 disk = float2(0.0, 0.0);
|
|
if (areaRadius > 0.0 && gEnableDenoiser == 0u)
|
|
disk = RayReconstructionTemporalDisk(pixel, 0xA11E00u + s, areaRadius);
|
|
else if (areaRadius > 0.0)
|
|
disk = ConcentricSampleDisk(Hammersley2D(s, sampleCount, rand)) * areaRadius;
|
|
|
|
float3 sampleLightPos = Lgt.position + tangent * disk.x + bitangent * disk.y;
|
|
float3 toLight = sampleLightPos - worldPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = toLight / dist;
|
|
|
|
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u && diffuseNoL > 0.0001)
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
|
|
if (Lgt.pointRadiusPad <= 0.5)
|
|
specAccum += ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo);
|
|
|
|
diffuseAccum += Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
|
|
}
|
|
|
|
float invSamples = 1.0 / (float)sampleCount;
|
|
specularOut = specAccum * invSamples;
|
|
return diffuseAccum * invSamples;
|
|
}
|
|
|
|
float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
|
|
{
|
|
specularOut = 0.0;
|
|
|
|
float3 toLight = Lgt.position - worldPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ComputeSpotLightAttenuation(worldPos, Lgt);
|
|
|
|
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u && diffuseNoL > 0.0001 && atten > 0.0)
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
|
|
if (Lgt.pointRadiusPad <= 0.5)
|
|
specularOut = ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo);
|
|
|
|
return Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
|
|
}
|
|
|
|
float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
|
|
{
|
|
specularOut = 0.0;
|
|
|
|
float3 toCenter = Lgt.position - worldPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float attenRadius = max(Lgt.radius, 1e-4);
|
|
float atten = saturate((attenRadius - centerDist) / attenRadius);
|
|
atten = atten * atten * atten * atten;
|
|
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
uint sampleCount = ClampRectLightSampleCount(Lgt.samples);
|
|
|
|
float3 diffuseAccum = 0.0;
|
|
float3 specAccum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < sampleCount; ++s)
|
|
{
|
|
float2 uv = StableRectLightSampleUV(s, sampleCount);
|
|
if (gEnableDenoiser == 0u)
|
|
uv = clamp(uv + RayReconstructionTemporalDisk(pixel, 0xEC7000u + s, 0.18), -0.85, 0.85);
|
|
|
|
float3 sampleLightPos =
|
|
Lgt.position +
|
|
Lgt.axisU * (uv.x * Lgt.halfWidth) +
|
|
Lgt.axisV * (uv.y * Lgt.halfHeight);
|
|
|
|
float3 sampleVec = sampleLightPos - worldPos;
|
|
float sampleDist = length(sampleVec);
|
|
if (sampleDist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = sampleVec / sampleDist;
|
|
float NdotL = ComputeDiffuseLightingTerm(N, L);
|
|
if (NdotL <= 0.0)
|
|
continue;
|
|
|
|
float faceTerm = (Lgt.twoSided != 0)
|
|
? abs(dot(-L, Lgt.normal))
|
|
: saturate(dot(-L, Lgt.normal));
|
|
|
|
if (faceTerm <= 0.0)
|
|
continue;
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u)
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, sampleDist);
|
|
|
|
if (Lgt.pointRadiusPad <= 0.5)
|
|
{
|
|
specAccum += ComputeSpecular(
|
|
N,
|
|
V,
|
|
L,
|
|
Lgt.color,
|
|
Lgt.intensity * faceTerm,
|
|
1.0,
|
|
shadow,
|
|
specularAlbedo) * atten;
|
|
}
|
|
|
|
diffuseAccum += clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0);
|
|
}
|
|
|
|
float invSamples = 1.0 / (float)sampleCount;
|
|
specularOut = specAccum * invSamples;
|
|
return diffuseAccum * invSamples;
|
|
}
|
|
|
|
float HenyeyGreensteinPhase(float cosTheta, float g)
|
|
{
|
|
g = clamp(g, -0.85, 0.85);
|
|
float g2 = g * g;
|
|
float denom = max(1.0 + g2 - 2.0 * g * cosTheta, 1e-3);
|
|
return (1.0 - g2) / max(4.0 * 3.14159265 * pow(denom, 1.5), 1e-3);
|
|
}
|
|
|
|
bool IsPointInsideLightVolume(float3 samplePos, Light Lgt)
|
|
{
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
float3 radii = GetPointLightRadius(Lgt);
|
|
float3 offset = samplePos - Lgt.position;
|
|
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
float3 axisW = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
|
|
return abs(dot(offset, axisU)) < radii.x &&
|
|
abs(dot(offset, axisV)) < radii.y &&
|
|
abs(dot(offset, axisW)) < radii.z;
|
|
}
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
float3 lightToSample = samplePos - Lgt.position;
|
|
|
|
float nearClip = max(Lgt.pointRadius.x, 0.0);
|
|
float farClip = max(Lgt.radius, nearClip + 1e-4);
|
|
float3 spotDir = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
float depth = dot(lightToSample, spotDir);
|
|
|
|
if (depth <= nearClip || depth >= farClip)
|
|
return false;
|
|
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
float invDepth = 1.0 / max(depth, 1e-4);
|
|
|
|
float signedU = (dot(lightToSample, axisU) * invDepth) / max(abs(Lgt.halfWidth), 1e-4);
|
|
float signedV = (dot(lightToSample, axisV) * invDepth) / max(abs(Lgt.halfHeight), 1e-4);
|
|
|
|
return abs(signedU) < 1.0 && abs(signedV) < 1.0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float ComputeLightVolumeAttenuation(float3 samplePos, Light Lgt)
|
|
{
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
return ComputePointLightAttenuation(samplePos, Lgt);
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
return ComputeSpotLightAttenuation(samplePos, Lgt);
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
float EstimateVolumeDensityFromLight(Light Lgt)
|
|
{
|
|
// Doom 3 world units are large. Tie the default participating-medium density
|
|
// to light range so the caller only needs one artist-facing attribute.
|
|
float range = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
? GetPointLightMaxRadius(Lgt)
|
|
: max(Lgt.radius, 1.0);
|
|
|
|
return clamp(2.25 / max(range, 32.0), 0.0015, 0.035);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 EstimateSingleLightVolumetricScattering(uint2 pixel, float3 cameraPos, float3 worldPos, Light Lgt, inout uint rng)
|
|
{
|
|
if (Lgt.volumetricScattering <= 0.0)
|
|
return 0.0;
|
|
|
|
if (Lgt.type != GL_RAYTRACING_LIGHT_TYPE_POINT && Lgt.type != GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
return 0.0;
|
|
|
|
float3 cameraToSurface = worldPos - cameraPos;
|
|
float viewDist = length(cameraToSurface);
|
|
if (viewDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 viewDir = cameraToSurface / viewDist;
|
|
|
|
uint stepCount = (uint)ceil(viewDist / 64.0);
|
|
stepCount = min(max(stepCount, 8u), 32u);
|
|
float stepLen = viewDist / (float)stepCount;
|
|
|
|
float jitter = 0.5;
|
|
if (gEnableDenoiser == 0u)
|
|
{
|
|
uint jitterRng = InitRng(pixel, gFrameIndex, 0x701u);
|
|
jitter = lerp(0.42, 0.58, Rand(jitterRng));
|
|
}
|
|
|
|
float density = EstimateVolumeDensityFromLight(Lgt);
|
|
float anisotropy = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) ? 0.55 : 0.35;
|
|
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < stepCount; ++s)
|
|
{
|
|
float t = ((float)s + jitter) * stepLen;
|
|
t = min(t, viewDist - 0.001);
|
|
|
|
float3 samplePos = cameraPos + viewDir * t;
|
|
if (!IsPointInsideLightVolume(samplePos, Lgt))
|
|
continue;
|
|
|
|
float3 toLight = Lgt.position - samplePos;
|
|
float lightDist = length(toLight);
|
|
if (lightDist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = toLight / lightDist;
|
|
|
|
float visibility = 1.0;
|
|
if (Lgt.samples != 0u)
|
|
{
|
|
float3 shadowOrigin = samplePos + L * (gShadowBias * 0.75) + viewDir * (gShadowBias * 0.15);
|
|
visibility = TraceShadow(shadowOrigin, L, max(lightDist - gShadowBias, 0.001));
|
|
}
|
|
if (visibility <= 0.0)
|
|
continue;
|
|
|
|
float atten = ComputeLightVolumeAttenuation(samplePos, Lgt);
|
|
if (atten <= 0.0)
|
|
continue;
|
|
|
|
float phase = HenyeyGreensteinPhase(dot(L, viewDir), anisotropy);
|
|
float transmittance = exp(-density * t);
|
|
float slice = density * stepLen;
|
|
|
|
accum += Lgt.color * (Lgt.intensity * atten * visibility * phase * transmittance * slice);
|
|
}
|
|
|
|
// Scale from normalized phase-function energy into a game-facing glow term.
|
|
// The user-facing light attribute still controls the final strength.
|
|
const float DOOM3_VOLUME_SCALE = 7.5;
|
|
return clamp(accum * max(Lgt.volumetricScattering, 0.0) * DOOM3_VOLUME_SCALE, 0.0, 12.0);
|
|
}
|
|
|
|
float3 EstimatePathTracedVolumetricScattering(uint2 pixel, float3 worldPos, inout uint rng)
|
|
{
|
|
float3 volume = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
if (Lgt.volumetricScattering <= 0.0)
|
|
continue;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT || Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
volume += EstimateSingleLightVolumetricScattering(pixel, gCameraPos.xyz, worldPos, Lgt, rng);
|
|
}
|
|
|
|
return volume;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
|
|
{
|
|
// Cheap unshadowed estimate used as the all-lights baseline for secondary
|
|
// GI. A small shadowed subset below corrects this baseline so every light
|
|
// still bounces, but important occlusion is no longer missing from GI.
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
float3 toLight = Lgt.position - hitPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ComputePointLightAttenuation(hitPos, Lgt);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
float3 toLight = Lgt.position - hitPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ComputeSpotLightAttenuation(hitPos, Lgt);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
float3 toCenter = Lgt.position - hitPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float attenRadius = max(Lgt.radius, 1e-4);
|
|
float atten = saturate((attenRadius - centerDist) / attenRadius);
|
|
atten = atten * atten;
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float3 L = toCenter / centerDist;
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
if (nDotL <= 0.0)
|
|
return 0.0;
|
|
|
|
float faceTerm = (Lgt.twoSided != 0)
|
|
? abs(dot(-L, Lgt.normal))
|
|
: saturate(dot(-L, Lgt.normal));
|
|
|
|
if (faceTerm <= 0.0)
|
|
return 0.0;
|
|
|
|
return clamp(Lgt.color * (Lgt.intensity * nDotL * faceTerm * atten), 0.0, 8.0);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, Light Lgt, inout uint rng)
|
|
{
|
|
float3 spec = 0.0;
|
|
float3 diffuse = 0.0;
|
|
float3 hitSpecularAlbedo = LoadSceneSpecularAlbedo(hitPixel, hitAlbedo);
|
|
|
|
// Use the same visibility-capable direct-light samplers as the primary hit.
|
|
// This supplies proper next-event estimation at secondary hits instead of the
|
|
// old unoccluded light-list approximation.
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
|
}
|
|
|
|
return clamp(diffuse, 0.0, 12.0);
|
|
}
|
|
|
|
float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
uint skySamples = (gSamplesPerPixel >= 4u) ? 2u : 1u;
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0u; i < skySamples; ++i)
|
|
{
|
|
float3 skyDir = (i == 0u)
|
|
? SampleConeWorld(GetSkyLightDirection10AM(), 0.18, rng)
|
|
: SampleCosineWorld(hitN, rng);
|
|
|
|
float NoSky = saturate(dot(hitN, skyDir));
|
|
if (NoSky <= 0.0)
|
|
continue;
|
|
|
|
float visibility = TraceVisibilityBiased(hitPos, hitN, skyDir, SKY_TMAX);
|
|
accum += GetSkyRadiance(skyDir) * visibility * NoSky;
|
|
}
|
|
|
|
return accum / (float)skySamples;
|
|
}
|
|
|
|
float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, inout uint rng)
|
|
{
|
|
bool hitIsSkeletal = (hitGeoFlag & GEOMETRY_FLAG_SKELETAL) != 0u;
|
|
bool hitIsUnlit = (hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
|
|
|
|
hitN = SafeNormalizeOr(hitN, float3(0.0, 0.0, 1.0));
|
|
hitV = SafeNormalizeOr(hitV, -hitN);
|
|
hitAlbedo = saturate(hitAlbedo);
|
|
|
|
// Treat unlit G-buffer surfaces as simple bounce cards. Glow-map emissive is
|
|
// intentionally excluded here so visible emissive no longer casts GI/light.
|
|
if (hitIsUnlit)
|
|
return clamp(hitAlbedo * 2.0 + GetSkyRadiance(hitN) * 0.04, 0.0, 6.0);
|
|
|
|
float upness = saturate(hitN.z * 0.5 + 0.5);
|
|
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
|
|
|
|
// Real occluded sky contribution at the secondary hit. The previous GI path
|
|
// used a fixed sky term, so corners/cavities received too much indirect light.
|
|
lighting += EstimateBounceSkyLighting(hitPos, hitN, rng) * (0.14 + 0.10 * upness);
|
|
|
|
// Glow-map emissive no longer participates in secondary GI. It remains a
|
|
// direct visible/bloom-only effect until real material-space emissive lighting
|
|
// is implemented.
|
|
|
|
// Baseline: every light contributes to bounced radiance, so small dynamic
|
|
// lights do not vanish just because they were not chosen by the stochastic
|
|
// next-event-estimation budget.
|
|
float3 fastAllLights = 0.0;
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]);
|
|
}
|
|
lighting += fastAllLights;
|
|
|
|
// Keep secondary light response deterministic. A rotating shadowed-light
|
|
// correction looked good in stills but caused bright Doom lights to sparkle
|
|
// badly in GI because only a few lights were corrected each frame.
|
|
|
|
//if (hitIsSkeletal)
|
|
// lighting *= 1.10;
|
|
|
|
// Outgoing diffuse radiance from the bounce surface. The primary surface's
|
|
// albedo is applied later in RayGen, so only the secondary hit albedo belongs
|
|
// here.
|
|
return clamp(hitAlbedo * max(lighting, 0.0), 0.0, 16.0);
|
|
}
|
|
|
|
)"
|
|
R"(
|
|
float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
|
|
{
|
|
// Disabled on purpose. This used to sample nearby camera G-buffer pixels and
|
|
// was therefore screen-space lighting. Geometry emitters now light the scene
|
|
// only through the DXR light list and TraceShadow() visibility rays.
|
|
return 0.0;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
|
|
{
|
|
// Disabled until this renderer has real per-hit material/emissive buffers in
|
|
// the SBT. The old implementation traced a ray, then projected the hit back
|
|
// into the current camera G-buffer with the current camera G-buffer, which made
|
|
// indirect light depend on what was visible on screen. Direct area lighting
|
|
// below is still fully ray traced through the TLAS.
|
|
return 0.0;
|
|
}
|
|
|
|
float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
|
|
{
|
|
// No screen-space material fallback and no stochastic diffuse GI in the fast
|
|
// geometry-emitter path. Area lights are evaluated by PathTraceDirect* with
|
|
// DXR visibility rays.
|
|
return 0.0;
|
|
}
|
|
|
|
float3 ApplyPrimaryDiffusePost(float3 lightingAccum, float ao, float microShadow, bool isSkeletal)
|
|
{
|
|
if (isSkeletal)
|
|
{
|
|
ao = lerp(ao, 1.0, 0.35);
|
|
microShadow = lerp(microShadow, 1.0, 0.65);
|
|
}
|
|
|
|
lightingAccum *= ao;
|
|
lightingAccum *= microShadow;
|
|
|
|
//if (isSkeletal)
|
|
// lightingAccum *= 1.2;
|
|
|
|
return max(lightingAccum, 0.0);
|
|
}
|
|
|
|
float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal)
|
|
{
|
|
// AO is a diffuse/ambient visibility term here. Multiplying specular by AO
|
|
// directly made highlights disappear on creases, props, and normal-mapped
|
|
// surfaces. Keep a mild occlusion tint, but let direct-light specular read.
|
|
float specAo = lerp(0.58, 1.0, saturate(ao));
|
|
if (isSkeletal)
|
|
specAo = lerp(specAo, 1.0, 0.50);
|
|
specularAccum *= specAo;
|
|
|
|
//if (isSkeletal)
|
|
// specularAccum *= 1.15;
|
|
|
|
return max(specularAccum, 0.0);
|
|
}
|
|
|
|
float3 PathTraceDeterministicLighting(
|
|
uint2 pixel,
|
|
float3 worldPos,
|
|
float3 N,
|
|
float3 V,
|
|
float3 baseAlbedo,
|
|
float3 specularAlbedo,
|
|
bool isSkeletal,
|
|
float cavity,
|
|
float ao,
|
|
float skyVis,
|
|
float ambientSkyVis,
|
|
out float3 specularAccum)
|
|
{
|
|
specularAccum = 0.0;
|
|
|
|
float microShadow = 1.0;
|
|
|
|
// These environment and direct-light terms are deterministic for a given
|
|
// pixel. The old RayGen evaluated them once for every SPP, which multiplied
|
|
// the AO/sky/direct shadow ray budget without adding new samples.
|
|
float upness = saturate(N.z * 0.5 + 0.5);
|
|
float3 skyColorRGB = float3(0.98, 0.55, 0.35);
|
|
float3 skyColor = skyColorRGB * (0.35 + 0.65 * upness);
|
|
|
|
float3 lightingAccum = gAmbientColor.rgb * (gAmbientColor.a * 0.04);
|
|
lightingAccum += skyColor * (0.42 * skyVis);
|
|
lightingAccum += ambientSkyVis * (skyColorRGB * 0.12);
|
|
|
|
// Glow-map emissive is direct/bloom-only for now. It is added after lighting
|
|
// in RayGen and is intentionally not injected into lightingAccum.
|
|
|
|
//if (isSkeletal)
|
|
// lightingAccum += 0.1;
|
|
|
|
// The current direct-light evaluators are deterministic. Keep the inout RNG
|
|
// argument only because the functions share the same signature as stochastic
|
|
// helpers; it is not consumed by PathTraceDirect* in the current shader.
|
|
uint directRng = InitRng(pixel, 0u, 0xD17EC7u);
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
float3 spec = 0.0;
|
|
float3 diffuse = 0.0;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
|
}
|
|
|
|
lightingAccum += diffuse;
|
|
specularAccum += spec;
|
|
}
|
|
|
|
specularAccum = ApplyPrimarySpecularPost(specularAccum, ao, isSkeletal);
|
|
return ApplyPrimaryDiffusePost(lightingAccum, ao, microShadow, isSkeletal);
|
|
}
|
|
|
|
|
|
float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir)
|
|
{
|
|
// Reflection rays can hit off-screen or camera-hidden TLAS geometry. In that
|
|
// case this pass has no per-triangle material/normal table, so use a neutral
|
|
// lit card instead of returning black. This keeps reflected objects visible
|
|
// without pretending every unknown hit is a perfect emissive surface.
|
|
incomingViewDir = SafeNormalizeOr(incomingViewDir, -hitNormal);
|
|
hitNormal = SafeNormalizeOr(hitNormal, incomingViewDir);
|
|
|
|
if (dot(hitNormal, incomingViewDir) < 0.0)
|
|
hitNormal = -hitNormal;
|
|
|
|
float upness = saturate(hitNormal.z * 0.5 + 0.5);
|
|
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
|
|
lighting += GetSkyRadiance(hitNormal) * (0.12 + 0.08 * upness);
|
|
lighting += GetSkyRadiance(SafeNormalizeOr(reflect(-incomingViewDir, hitNormal), hitNormal)) * 0.055;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
lighting += EstimateFastBounceLight(hitPos, hitNormal, gLights[i]) * 0.78;
|
|
}
|
|
|
|
const float3 NEUTRAL_UNKNOWN_ALBEDO = float3(0.58, 0.58, 0.58);
|
|
return clamp(NEUTRAL_UNKNOWN_ALBEDO * max(lighting, 0.0), 0.0, 10.0);
|
|
}
|
|
|
|
|
|
float ComputeSpecularReflectionLocalEnergy(float3 worldPos, float3 N)
|
|
{
|
|
float energy = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
|
|
float atten = 0.0;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
atten = ComputePointLightAttenuation(worldPos, Lgt);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
atten = ComputeSpotLightAttenuation(worldPos, Lgt);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
float dist = length(Lgt.position - worldPos);
|
|
float range = max(Lgt.radius, 1.0);
|
|
atten = saturate((range - dist) / range);
|
|
atten = atten * atten;
|
|
}
|
|
|
|
if (atten <= 0.0)
|
|
continue;
|
|
|
|
float3 toLight = SafeNormalizeOr(Lgt.position - worldPos, N);
|
|
float facing = saturate(dot(N, toLight));
|
|
float lightPeak = max(max(Lgt.color.r, Lgt.color.g), Lgt.color.b);
|
|
|
|
energy += atten * facing * Lgt.intensity * lightPeak;
|
|
}
|
|
|
|
// Keep reflections tied to nearby light contribution instead of becoming
|
|
// a global mirror pass.
|
|
return saturate(energy * 0.08);
|
|
}
|
|
|
|
float ComputeSpecularReflectionMaxDistance(float3 worldPos)
|
|
{
|
|
float maxDistance = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
|
|
float atten = 0.0;
|
|
float range = 0.0;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
atten = ComputePointLightAttenuation(worldPos, Lgt);
|
|
range = GetPointLightMaxRadius(Lgt);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
atten = ComputeSpotLightAttenuation(worldPos, Lgt);
|
|
range = max(Lgt.radius, 1.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
float dist = length(Lgt.position - worldPos);
|
|
range = max(Lgt.radius, 1.0);
|
|
atten = saturate((range - dist) / range);
|
|
atten = atten * atten;
|
|
}
|
|
|
|
if (atten <= 0.0)
|
|
continue;
|
|
|
|
// Reflections should not reach the full light range like a mirror.
|
|
// This makes the reflection proportional to local light influence.
|
|
maxDistance = max(maxDistance, range * lerp(0.18, 0.55, saturate(atten)));
|
|
}
|
|
|
|
return clamp(maxDistance, 24.0, 768.0);
|
|
}
|
|
|
|
float ComputeSpecularReflectionDistanceFade(float hitT, float maxT)
|
|
{
|
|
float t = saturate(hitT / max(maxT, 1.0));
|
|
|
|
// Strong near reflection, smooth fade before the end of the local volume.
|
|
float fade = 1.0 - smoothstep(0.35, 1.0, t);
|
|
|
|
return fade * fade;
|
|
}
|
|
|
|
float3 EstimateRayTracedSpecularReflection(
|
|
uint2 pixel,
|
|
float3 worldPos,
|
|
float3 N,
|
|
float3 V,
|
|
float3 baseAlbedo,
|
|
float3 specularAlbedo,
|
|
float cavity,
|
|
inout uint rng)
|
|
{
|
|
// Disabled for now. The previous reflection path traced TLAS hits but then
|
|
// used the camera G-buffer to fetch off-screen material data, which is a
|
|
// screen-space approximation and very expensive. Direct specular from real
|
|
// lights is still computed in PathTraceDeterministicLighting().
|
|
return 0.0;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 ApplyRealisticOutputCurve(float3 color)
|
|
{
|
|
// Final photographic shoulder only: it does not change light radius or
|
|
// attenuation, but it prevents intense local lights/specular/bloom from
|
|
// clipping into a flat white patch. Values below 1.0 are left untouched.
|
|
color = max(color, 0.0);
|
|
|
|
float peak = max(max(color.r, color.g), color.b);
|
|
if (peak > 1.0)
|
|
{
|
|
float over = peak - 1.0;
|
|
float shoulderPeak = 1.0 + over / (1.0 + over * 0.38);
|
|
color *= shoulderPeak / max(peak, 1.0e-5);
|
|
}
|
|
|
|
return max(color, 0.0);
|
|
}
|
|
|
|
[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));
|
|
float4 emissiveSample = gEmissiveTex.Load(int3(pixel, 0));
|
|
float3 emissiveSurface = CompressEmissiveRadiance(emissiveSample.rgb, 6.50);
|
|
float depthSample = gDepthTex.Load(int3(pixel, 0));
|
|
|
|
float3 emissiveBloom = EstimateEmissiveBloomAtPixel(pixel);
|
|
|
|
if (depthSample <= 0.0 || depthSample >= 1.0)
|
|
{
|
|
gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(albedoSample.rgb + emissiveSurface + emissiveBloom), albedoSample.a);
|
|
return;
|
|
}
|
|
|
|
float3 baseAlbedo = albedoSample.rgb;
|
|
float3 specularAlbedo = LoadSceneSpecularAlbedo(pixel, baseAlbedo);
|
|
float4 positionSample = gPositionTex.Load(int3(pixel, 0));
|
|
float3 worldPos = positionSample.xyz;
|
|
uint geoFlag = DecodeGeometryFlag(positionSample.w);
|
|
bool isSkeletal = (geoFlag & GEOMETRY_FLAG_SKELETAL) != 0u;
|
|
bool isUnlit = (geoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
|
|
float4 normalSample = LoadSceneNormal(pixel);
|
|
float3 N = EnhanceBumpNormal(pixel, worldPos, baseAlbedo, isSkeletal);
|
|
float3 V = normalize(gCameraPos.xyz - worldPos);
|
|
|
|
if (isUnlit)
|
|
{
|
|
gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(baseAlbedo + emissiveSurface + emissiveBloom), albedoSample.a);
|
|
return;
|
|
}
|
|
|
|
uint spp = max(gSamplesPerPixel, 1u);
|
|
spp = min(spp, 8u);
|
|
|
|
// All four of these are deterministic for this pixel. Compute them once,
|
|
// then reuse them for every stochastic GI sample.
|
|
float cavity = 1.0; // screen-space cavity disabled for ray-traced-only lighting
|
|
float ao = 1.0; // expensive ray AO disabled in the fast area-light path
|
|
float skyVis = 0; //ComputeSkyVisibility(worldPos, N, pixel);
|
|
float ambientSkyVis = 0; //TraceStraightUpToSky(worldPos, N);
|
|
float microShadow = 1.0;
|
|
// Screen-space final gather is disabled. Geometry emitters illuminate only
|
|
// through the DXR rect-light path below.
|
|
float3 reactiveFinalGather = 0.0;
|
|
float3 specularAccum = 0.0;
|
|
float3 lightingAccum = PathTraceDeterministicLighting(
|
|
pixel,
|
|
worldPos,
|
|
N,
|
|
V,
|
|
baseAlbedo,
|
|
specularAlbedo,
|
|
isSkeletal,
|
|
cavity,
|
|
ao,
|
|
skyVis,
|
|
ambientSkyVis,
|
|
specularAccum);
|
|
lightingAccum += EstimateScreenSpaceEmissiveParticleLighting(pixel, worldPos, N);
|
|
// No stochastic indirect bounce in this fast path. The area emitters are
|
|
// sampled as true rect lights with DXR shadow rays in the direct-light loop.
|
|
float3 reflectedSpecular = 0.0;
|
|
float3 albedo = baseAlbedo * cavity;
|
|
float3 finalColor = (albedo * lightingAccum) + specularAccum + reflectedSpecular;
|
|
finalColor += reactiveFinalGather;
|
|
float outputAo = isSkeletal ? lerp(ao, 1.0, 0.55) : ao;
|
|
finalColor *= clamp(outputAo + 0.6, 0.0, 1.0);
|
|
|
|
// Volumetric light scattering is radiance in the camera ray, not surface
|
|
// reflectance, so add it after surface albedo/specular composition. Because
|
|
// it is written into the same path-trace target, the internal a-trous pass
|
|
// denoises the stochastic volume/GI signal together with the rest of the ray result.
|
|
uint volumeRng = InitRng(pixel, 0u, 0x51u);
|
|
finalColor += EstimatePathTracedVolumetricScattering(pixel, worldPos, volumeRng);
|
|
|
|
// Visible self-emission from the primary surface. Screen-space particle
|
|
// emissive lighting was already injected into lightingAccum above.
|
|
finalColor += emissiveSurface + emissiveBloom;
|
|
|
|
gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(finalColor), albedoSample.a);
|
|
}
|
|
)";
|
|
|
|
static const char* g_glRaytracingDenoiseHlsl = R"(
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
};
|
|
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
Texture2D<float4> gPathTraceTex : register(t6);
|
|
Texture2D<float4> gDenoiseATex : register(t7);
|
|
Texture2D<float4> gDenoiseBTex : register(t8);
|
|
Texture2D<float4> gTemporalTex : register(t10);
|
|
|
|
RWTexture2D<float4> gRayOutputTex : register(u0);
|
|
RWTexture2D<float4> gDenoiseAOutTex : register(u1);
|
|
RWTexture2D<float4> gDenoiseBOutTex : register(u2);
|
|
RWTexture2D<float4> gDenoisedOutputTex : register(u3);
|
|
|
|
static const float kKernel[5] = { 0.0625, 0.25, 0.375, 0.25, 0.0625 };
|
|
|
|
float3 SafeNormal(float3 n)
|
|
{
|
|
float lenSq = max(dot(n, n), 1e-8);
|
|
return n * rsqrt(lenSq);
|
|
}
|
|
|
|
float Luminance(float3 c)
|
|
{
|
|
return dot(c, float3(0.2126, 0.7152, 0.0722));
|
|
}
|
|
|
|
static const uint GEOMETRY_FLAG_GLASS = 4u;
|
|
|
|
uint DecodeGeometryFlag(float geoFlag)
|
|
{
|
|
return (uint)floor(max(geoFlag, 0.0) + 0.5);
|
|
}
|
|
|
|
float3 SafeAlbedoDivisor(float3 albedo)
|
|
{
|
|
// Do not let black/dark textures explode when demodulating noisy lighting.
|
|
return max(abs(albedo), float3(0.06, 0.06, 0.06));
|
|
}
|
|
|
|
float3 DemodulateLighting(float3 radiance, float3 albedo)
|
|
{
|
|
return radiance / SafeAlbedoDivisor(albedo);
|
|
}
|
|
|
|
float3 RemodulateLighting(float3 lighting, float3 albedo)
|
|
{
|
|
return lighting * SafeAlbedoDivisor(albedo);
|
|
}
|
|
|
|
float4 LoadDenoiseSource(int2 p)
|
|
{
|
|
if (gDenoisePassIndex == 0u)
|
|
return gTemporalTex.Load(int3(p, 0));
|
|
if (gDenoisePassIndex == 1u)
|
|
return gDenoiseATex.Load(int3(p, 0));
|
|
return gDenoiseBTex.Load(int3(p, 0));
|
|
}
|
|
|
|
void StoreDenoiseOutput(uint2 p, float4 v)
|
|
{
|
|
if (gDenoisePassIndex == 0u)
|
|
gDenoiseAOutTex[p] = v;
|
|
else if (gDenoisePassIndex == 1u)
|
|
gDenoiseBOutTex[p] = v;
|
|
else
|
|
gDenoisedOutputTex[p] = v;
|
|
}
|
|
|
|
float GeometryAwareWeight(
|
|
float3 centerRadiance,
|
|
float3 sampleRadiance,
|
|
float3 centerAlbedo,
|
|
float3 sampleAlbedo,
|
|
float3 centerNormal,
|
|
float3 sampleNormal,
|
|
float3 centerPos,
|
|
float3 samplePos,
|
|
float centerDepth,
|
|
float sampleDepth,
|
|
uint centerGeoFlag,
|
|
uint sampleGeoFlag,
|
|
float kernelWeight)
|
|
{
|
|
if (sampleDepth <= 0.0 || sampleDepth >= 1.0)
|
|
return 0.0;
|
|
|
|
// Do not smear lighting across material-class boundaries. This is
|
|
// particularly important for glass, because the primary G-buffer sample can
|
|
// be glass while the ray visibility must continue through it.
|
|
if (((centerGeoFlag ^ sampleGeoFlag) & GEOMETRY_FLAG_GLASS) != 0u)
|
|
return 0.0;
|
|
|
|
float3 centerLighting = DemodulateLighting(centerRadiance, centerAlbedo);
|
|
float3 sampleLighting = DemodulateLighting(sampleRadiance, sampleAlbedo);
|
|
|
|
// Use albedo, not noisy lit radiance, as the main color edge guide. The
|
|
// previous filter used the shadowed/noisy signal itself as the guide, which
|
|
// rejected neighbors across shadow variation and left shadow noise intact.
|
|
float albedoDiff = length(centerAlbedo - sampleAlbedo);
|
|
float albedoWeight = exp(-albedoDiff * max(gDenoisePhiColor, 0.001));
|
|
|
|
// A deliberately soft lighting-domain gate keeps hard contact-shadow edges
|
|
// from being over-blurred, but still lets noisy penumbra/visibility samples
|
|
// converge across the same surface.
|
|
float centerLum = Luminance(centerLighting);
|
|
float sampleLum = Luminance(sampleLighting);
|
|
float illumDiff = abs(sampleLum - centerLum);
|
|
float relativeIllumDiff = illumDiff / max(max(abs(centerLum), abs(sampleLum)), 0.05);
|
|
|
|
// Keep this gate conservative. The noise fix is to stabilize and stratify the
|
|
// ray samples; over-loosening this filter smears direct lighting and makes the
|
|
// scene look noisier/blotchier.
|
|
float illuminationWeight = exp(-relativeIllumDiff * max(gDenoisePhiColor * 0.035, 0.10));
|
|
|
|
float normalWeight = pow(saturate(dot(centerNormal, sampleNormal)), max(gDenoisePhiNormal, 1.0));
|
|
float positionDiff = length(samplePos - centerPos);
|
|
float positionWeight = exp(-positionDiff * max(gDenoisePhiPosition, 0.001));
|
|
float depthDiff = abs(sampleDepth - centerDepth);
|
|
float depthWeight = exp(-depthDiff * 300.0);
|
|
|
|
return kernelWeight * albedoWeight * illuminationWeight * normalWeight * positionWeight * depthWeight;
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
uint2 pixel = dispatchThreadId.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));
|
|
float4 centerSource = LoadDenoiseSource(int2(pixel));
|
|
|
|
if (depthSample <= 0.0 || depthSample >= 1.0)
|
|
{
|
|
// Preserve raygen's emissive bloom on background/no-depth pixels.
|
|
// Returning albedo here would erase the halo whenever the internal
|
|
// temporal/a-trous denoiser is active.
|
|
StoreDenoiseOutput(pixel, centerSource);
|
|
return;
|
|
}
|
|
|
|
if (gEnableDenoiser == 0u)
|
|
{
|
|
StoreDenoiseOutput(pixel, centerSource);
|
|
return;
|
|
}
|
|
|
|
float3 centerAlbedo = saturate(albedoSample.rgb);
|
|
float3 centerNormal = SafeNormal(gNormalTex.Load(int3(pixel, 0)).xyz);
|
|
float4 centerPos4 = gPositionTex.Load(int3(pixel, 0));
|
|
float3 centerPos = centerPos4.xyz;
|
|
uint centerGeoFlag = DecodeGeometryFlag(centerPos4.w);
|
|
|
|
int stepI = max((int)round(max(gDenoiseStepWidth, 1.0)), 1);
|
|
float3 accumLighting = 0.0;
|
|
float weightSum = 0.0;
|
|
|
|
// Three-pass a-trous wavelet filter. The CPU dispatches this with step
|
|
// widths 1, 2, and 4. It is geometry-aware; temporal GI accumulation happens
|
|
// before this pass.
|
|
[unroll]
|
|
for (int ky = 0; ky < 5; ++ky)
|
|
{
|
|
[unroll]
|
|
for (int kx = 0; kx < 5; ++kx)
|
|
{
|
|
int2 sp = int2(pixel) + int2(kx - 2, ky - 2) * stepI;
|
|
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y)
|
|
continue;
|
|
|
|
float sampleDepth = gDepthTex.Load(int3(sp, 0));
|
|
float4 sampleColor4 = LoadDenoiseSource(sp);
|
|
float3 sampleAlbedo = saturate(gAlbedoTex.Load(int3(sp, 0)).rgb);
|
|
float3 sampleNormal = SafeNormal(gNormalTex.Load(int3(sp, 0)).xyz);
|
|
float4 samplePos4 = gPositionTex.Load(int3(sp, 0));
|
|
float3 samplePos = samplePos4.xyz;
|
|
uint sampleGeoFlag = DecodeGeometryFlag(samplePos4.w);
|
|
float kernelWeight = kKernel[kx] * kKernel[ky];
|
|
|
|
float w = GeometryAwareWeight(
|
|
centerSource.rgb,
|
|
sampleColor4.rgb,
|
|
centerAlbedo,
|
|
sampleAlbedo,
|
|
centerNormal,
|
|
sampleNormal,
|
|
centerPos,
|
|
samplePos,
|
|
depthSample,
|
|
sampleDepth,
|
|
centerGeoFlag,
|
|
sampleGeoFlag,
|
|
kernelWeight);
|
|
|
|
accumLighting += DemodulateLighting(sampleColor4.rgb, sampleAlbedo) * w;
|
|
weightSum += w;
|
|
}
|
|
}
|
|
|
|
float3 filteredLighting = (weightSum > 1e-6)
|
|
? (accumLighting / weightSum)
|
|
: DemodulateLighting(centerSource.rgb, centerAlbedo);
|
|
|
|
float3 filtered = RemodulateLighting(filteredLighting, centerAlbedo);
|
|
|
|
// Final-pass firefly clamp against the raw neighborhood.
|
|
if (gDenoisePassIndex >= 2u)
|
|
{
|
|
float3 minRaw = gPathTraceTex.Load(int3(pixel, 0)).rgb;
|
|
float3 maxRaw = minRaw;
|
|
|
|
[unroll]
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
[unroll]
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
int2 sp = int2(pixel) + int2(x, y);
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y)
|
|
continue;
|
|
float3 raw = gPathTraceTex.Load(int3(sp, 0)).rgb;
|
|
minRaw = min(minRaw, raw);
|
|
maxRaw = max(maxRaw, raw);
|
|
}
|
|
}
|
|
|
|
// Keep the clamp tight. A wide clamp lets bright stochastic GI/volume
|
|
// outliers survive and was the main reason the previous patch looked worse.
|
|
filtered = clamp(filtered, minRaw - 0.15, maxRaw + 0.15);
|
|
}
|
|
|
|
float3 outColor = lerp(centerSource.rgb, filtered, saturate(gDenoiseStrength));
|
|
StoreDenoiseOutput(pixel, float4(max(outColor, 0.0), centerSource.a));
|
|
}
|
|
)";
|
|
|
|
|
|
static const char* g_glRaytracingTemporalHlsl = R"(
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
};
|
|
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
Texture2D<float4> gPathTraceTex : register(t6);
|
|
Texture2D<float4> gHistoryTex : register(t9);
|
|
|
|
RWTexture2D<float4> gTemporalOutTex : register(u4);
|
|
RWTexture2D<float4> gHistoryOutTex : register(u5);
|
|
|
|
float LuminanceTemporal(float3 c)
|
|
{
|
|
return dot(c, float3(0.2126, 0.7152, 0.0722));
|
|
}
|
|
|
|
float3 SafeNormalTemporal(float3 n)
|
|
{
|
|
float lenSq = max(dot(n, n), 1e-8);
|
|
return n * rsqrt(lenSq);
|
|
}
|
|
|
|
float3 ClampHistoryToCurrent(float3 history, float3 current)
|
|
{
|
|
// Loose temporal clamp: it removes GI fireflies and old lighting while still
|
|
// allowing bright muzzle-flash/door-light changes to appear in a few frames.
|
|
float3 radius = 0.20 + abs(current) * 0.55;
|
|
return clamp(history, current - radius, current + radius);
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
uint2 pixel = dispatchThreadId.xy;
|
|
|
|
if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y)
|
|
return;
|
|
|
|
float4 raw = gPathTraceTex.Load(int3(pixel, 0));
|
|
float depth = gDepthTex.Load(int3(pixel, 0));
|
|
|
|
if (depth <= 0.0 || depth >= 1.0 || gMaxBounces <= 1u)
|
|
{
|
|
gTemporalOutTex[pixel] = raw;
|
|
gHistoryOutTex[pixel] = float4(raw.rgb, 0.0);
|
|
return;
|
|
}
|
|
|
|
float4 history = gHistoryTex.Load(int3(pixel, 0));
|
|
float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 31.0);
|
|
|
|
if (historyCount <= 0.0)
|
|
{
|
|
gTemporalOutTex[pixel] = raw;
|
|
gHistoryOutTex[pixel] = float4(raw.rgb, 1.0);
|
|
return;
|
|
}
|
|
|
|
float3 historyColor = ClampHistoryToCurrent(max(history.rgb, 0.0), max(raw.rgb, 0.0));
|
|
|
|
float rawLum = LuminanceTemporal(max(raw.rgb, 0.0));
|
|
float histLum = LuminanceTemporal(historyColor);
|
|
float relChange = abs(rawLum - histLum) / max(max(rawLum, histLum), 0.08);
|
|
|
|
// Base accumulation approaches 32 frames, but large lighting changes raise
|
|
// current-frame weight so the accumulator does not leave obvious trails.
|
|
float currentWeight = max(1.0 / (historyCount + 1.0), 0.055);
|
|
currentWeight = max(currentWeight, saturate(relChange * 0.28));
|
|
currentWeight = saturate(currentWeight);
|
|
|
|
float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight);
|
|
float nextCount = min(historyCount + 1.0, 31.0);
|
|
|
|
gTemporalOutTex[pixel] = float4(resolved, raw.a);
|
|
gHistoryOutTex[pixel] = float4(resolved, nextCount);
|
|
}
|
|
)";
|
|
|
|
static ComPtr<IDxcBlob> glRaytracingLightingCompileLibrary(const char* src)
|
|
{
|
|
ComPtr<IDxcUtils> utils;
|
|
ComPtr<IDxcCompiler3> compiler;
|
|
ComPtr<IDxcIncludeHandler> 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",
|
|
#if defined(_DEBUG)
|
|
L"-Zi",
|
|
L"-Qembed_debug",
|
|
#endif
|
|
// Keep the DXR library smaller to avoid long driver-side linking during
|
|
// CreateStateObject(). Compute/post shaders below still compile with O3.
|
|
L"-O1",
|
|
L"-all_resources_bound"
|
|
};
|
|
|
|
ComPtr<IDxcResult> 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<IDxcBlobUtf8> 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<IDxcBlob> dxil;
|
|
result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr);
|
|
return dxil;
|
|
}
|
|
|
|
static ComPtr<IDxcBlob> glRaytracingLightingCompileCompute(const char* src, const wchar_t* entryPoint)
|
|
{
|
|
ComPtr<IDxcUtils> utils;
|
|
ComPtr<IDxcCompiler3> compiler;
|
|
ComPtr<IDxcIncludeHandler> 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"-E", entryPoint,
|
|
L"-T", L"cs_6_0",
|
|
#if defined(_DEBUG)
|
|
L"-Zi",
|
|
L"-Qembed_debug",
|
|
#endif
|
|
L"-O3",
|
|
L"-all_resources_bound"
|
|
};
|
|
|
|
ComPtr<IDxcResult> result;
|
|
hr = compiler->Compile(&source, args, _countof(args), includeHandler.Get(), IID_PPV_ARGS(&result));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DXC compute compile failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlobUtf8> 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 compute compile status failed 0x%08X", (unsigned)status);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlob> dxil;
|
|
result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr);
|
|
return dxil;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateDescriptorHeap(void)
|
|
{
|
|
D3D12_DESCRIPTOR_HEAP_DESC hd = {};
|
|
hd.NumDescriptors = GLR_DESC_COUNT;
|
|
hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
hd.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateDescriptorHeap(
|
|
&hd,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.descriptorHeapRing[frame])));
|
|
}
|
|
|
|
g_glRaytracingLighting.descriptorHeap = g_glRaytracingLighting.descriptorHeapRing[0];
|
|
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 = GLR_DESC_SRV_COUNT;
|
|
ranges[0].BaseShaderRegister = 0;
|
|
ranges[0].RegisterSpace = 0;
|
|
ranges[0].OffsetInDescriptorsFromTableStart = 0;
|
|
|
|
ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
|
ranges[1].NumDescriptors = GLR_DESC_UAV_COUNT;
|
|
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<ID3DBlob> sig;
|
|
ComPtr<ID3DBlob> 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<ID3DBlob> sig;
|
|
ComPtr<ID3DBlob> 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 glRaytracingMapUploadBufferPersistent(const glRaytracingBuffer_t& buffer, void** mapped)
|
|
{
|
|
if (!buffer.resource || !mapped)
|
|
return 0;
|
|
|
|
*mapped = nullptr;
|
|
D3D12_RANGE readRange = {};
|
|
HRESULT hr = buffer.resource->Map(0, &readRange, mapped);
|
|
if (FAILED(hr) || !*mapped)
|
|
{
|
|
glRaytracingFatal("Persistent upload Map failed 0x%08X", (unsigned)hr);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingSelectFrameResources(UINT frameSlot)
|
|
{
|
|
frameSlot %= GL_RAYTRACING_CMD_RING_SIZE;
|
|
|
|
g_glRaytracingLighting.descriptorHeap = g_glRaytracingLighting.descriptorHeapRing[frameSlot];
|
|
g_glRaytracingLighting.constantBuffer = g_glRaytracingLighting.constantBufferRing[frameSlot];
|
|
g_glRaytracingLighting.lightBuffer = g_glRaytracingLighting.lightBufferRing[frameSlot];
|
|
g_glRaytracingLighting.constantBufferMapped = g_glRaytracingLighting.constantBufferMappedRing[frameSlot];
|
|
g_glRaytracingLighting.lightBufferMapped = g_glRaytracingLighting.lightBufferMappedRing[frameSlot];
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBuffer[i] = g_glRaytracingLighting.denoiseConstantBufferRing[frameSlot][i];
|
|
g_glRaytracingLighting.denoiseConstantBufferMapped[i] = g_glRaytracingLighting.denoiseConstantBufferMappedRing[frameSlot][i];
|
|
}
|
|
}
|
|
|
|
static int glRaytracingLightingCreateBuffers(void)
|
|
{
|
|
const UINT64 constantsBytes = glRaytracingAlignUp(sizeof(glRaytracingLightingConstants_t), 256);
|
|
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
g_glRaytracingLighting.constantBufferRing[frame] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
constantsBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.lightBufferRing[frame] = 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);
|
|
|
|
if (!g_glRaytracingLighting.constantBufferRing[frame].resource ||
|
|
!g_glRaytracingLighting.lightBufferRing[frame].resource)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
constantsBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource)
|
|
return 0;
|
|
}
|
|
|
|
// These are small UPLOAD-heap buffers updated every pass. Keeping every
|
|
// per-frame copy persistently mapped avoids Map/Unmap overhead and makes
|
|
// the async command-ring safe: the CPU never overwrites constants that an
|
|
// older in-flight command list still reads.
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.constantBufferRing[frame],
|
|
&g_glRaytracingLighting.constantBufferMappedRing[frame]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.lightBufferRing[frame],
|
|
&g_glRaytracingLighting.lightBufferMappedRing[frame]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i],
|
|
&g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i]))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
glRaytracingLightingSelectFrameResources(0);
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingUploadConstantsTo(
|
|
const glRaytracingBuffer_t& dst,
|
|
const glRaytracingLightingConstants_t& constants)
|
|
{
|
|
if (!dst.resource)
|
|
return;
|
|
|
|
void* mapped = nullptr;
|
|
if (dst.resource.Get() == g_glRaytracingLighting.constantBuffer.resource.Get())
|
|
{
|
|
mapped = g_glRaytracingLighting.constantBufferMapped;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (dst.resource.Get() == g_glRaytracingLighting.denoiseConstantBuffer[i].resource.Get())
|
|
{
|
|
mapped = g_glRaytracingLighting.denoiseConstantBufferMapped[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mapped)
|
|
{
|
|
memcpy(mapped, &constants, sizeof(constants));
|
|
return;
|
|
}
|
|
|
|
glRaytracingMapCopy(dst.resource.Get(), &constants, sizeof(constants));
|
|
}
|
|
|
|
static void glRaytracingLightingUpdateConstants(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return;
|
|
|
|
glRaytracingLightingUploadConstantsTo(
|
|
g_glRaytracingLighting.constantBuffer,
|
|
g_glRaytracingLighting.constants);
|
|
}
|
|
|
|
static void glRaytracingLightingUpdateLights(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return;
|
|
|
|
if (!g_glRaytracingLighting.lightBuffer.resource)
|
|
return;
|
|
|
|
if (g_glRaytracingLighting.cpuLights.empty())
|
|
return;
|
|
|
|
const size_t bytes = g_glRaytracingLighting.cpuLights.size() * sizeof(glRaytracingLight_t);
|
|
if (g_glRaytracingLighting.lightBufferMapped)
|
|
{
|
|
memcpy(g_glRaytracingLighting.lightBufferMapped, g_glRaytracingLighting.cpuLights.data(), bytes);
|
|
return;
|
|
}
|
|
|
|
glRaytracingMapCopy(
|
|
g_glRaytracingLighting.lightBuffer.resource.Get(),
|
|
g_glRaytracingLighting.cpuLights.data(),
|
|
bytes);
|
|
}
|
|
|
|
static void glRaytracingLightingUnmapUploadBuffers(void)
|
|
{
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
if (g_glRaytracingLighting.constantBufferRing[frame].resource &&
|
|
g_glRaytracingLighting.constantBufferMappedRing[frame])
|
|
{
|
|
g_glRaytracingLighting.constantBufferRing[frame].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.constantBufferMappedRing[frame] = nullptr;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.lightBufferRing[frame].resource &&
|
|
g_glRaytracingLighting.lightBufferMappedRing[frame])
|
|
{
|
|
g_glRaytracingLighting.lightBufferRing[frame].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.lightBufferMappedRing[frame] = nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource &&
|
|
g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i])
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_glRaytracingLighting.constantBufferMapped = nullptr;
|
|
g_glRaytracingLighting.lightBufferMapped = nullptr;
|
|
for (int i = 0; i < 3; ++i)
|
|
g_glRaytracingLighting.denoiseConstantBufferMapped[i] = nullptr;
|
|
}
|
|
|
|
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;
|
|
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
if (!g_glRaytracingLighting.descriptorHeapRing[frame] ||
|
|
!g_glRaytracingLighting.lightBufferRing[frame].resource)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE base =
|
|
g_glRaytracingLighting.descriptorHeapRing[frame]->GetCPUDescriptorHandleForHeapStart();
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.lightBufferRing[frame].resource.Get(),
|
|
&srv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
}
|
|
}
|
|
|
|
static int glRaytracingLightingCreateStateObject(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileLibrary(g_glRaytracingLightingHlsl);
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_EXPORT_DESC exports[8] = {};
|
|
exports[0].Name = L"RayGen";
|
|
exports[1].Name = L"ShadowMiss";
|
|
exports[2].Name = L"ShadowAnyHit";
|
|
exports[3].Name = L"ShadowClosestHit";
|
|
exports[4].Name = L"BounceMiss";
|
|
exports[5].Name = L"BounceAnyHit";
|
|
exports[6].Name = L"BounceClosestHit";
|
|
exports[7].Name = L"ReflectionClosestHit";
|
|
|
|
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 hitGroups[3] = {};
|
|
hitGroups[0].HitGroupExport = L"ShadowHitGroup";
|
|
hitGroups[0].AnyHitShaderImport = L"ShadowAnyHit";
|
|
hitGroups[0].ClosestHitShaderImport = L"ShadowClosestHit";
|
|
hitGroups[0].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
hitGroups[1].HitGroupExport = L"BounceHitGroup";
|
|
hitGroups[1].AnyHitShaderImport = L"BounceAnyHit";
|
|
hitGroups[1].ClosestHitShaderImport = L"BounceClosestHit";
|
|
hitGroups[1].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
hitGroups[2].HitGroupExport = L"ReflectionHitGroup";
|
|
hitGroups[2].ClosestHitShaderImport = L"ReflectionClosestHit";
|
|
hitGroups[2].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {};
|
|
shaderConfig.MaxPayloadSizeInBytes = 16; // BouncePayload: uint + float + uint + uint.
|
|
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[9] = {};
|
|
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 = &hitGroups[0];
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
|
|
subobjects[sub].pDesc = &hitGroups[1];
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
|
|
subobjects[sub].pDesc = &hitGroups[2];
|
|
++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",
|
|
L"BounceMiss",
|
|
L"BounceHitGroup",
|
|
L"ReflectionHitGroup"
|
|
};
|
|
|
|
D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION assoc = {};
|
|
assoc.pSubobjectToAssociate = &subobjects[6];
|
|
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* shadowMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowMiss");
|
|
void* bounceMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceMiss");
|
|
void* shadowHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup");
|
|
void* bounceHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceHitGroup");
|
|
void* reflectionHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ReflectionHitGroup");
|
|
|
|
if (!raygenId || !shadowMissId || !bounceMissId || !shadowHitId || !bounceHitId || !reflectionHitId)
|
|
{
|
|
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);
|
|
const UINT missTableSize = recordSize * 2u;
|
|
const UINT hitTableSize = recordSize * 3u;
|
|
|
|
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(),
|
|
missTableSize,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.hitTable = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
hitTableSize,
|
|
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;
|
|
}
|
|
|
|
std::vector<uint8_t> temp;
|
|
temp.resize((size_t)max(recordSize, max(missTableSize, hitTableSize)), 0);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), raygenId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.raygenTable.resource.Get(), temp.data(), recordSize);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), shadowMissId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize, bounceMissId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.missTable.resource.Get(), temp.data(), missTableSize);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), shadowHitId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize, bounceHitId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize * 2u, reflectionHitId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp.data(), hitTableSize);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateDenoisePipeline(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileCompute(g_glRaytracingDenoiseHlsl, L"DenoiseCS");
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_COMPUTE_PIPELINE_STATE_DESC pso = {};
|
|
pso.pRootSignature = g_glRaytracingLighting.globalRootSig.Get();
|
|
pso.CS.pShaderBytecode = dxil->GetBufferPointer();
|
|
pso.CS.BytecodeLength = dxil->GetBufferSize();
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateComputePipelineState(
|
|
&pso,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.denoisePSO)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int glRaytracingLightingCreateTemporalPipeline(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileCompute(g_glRaytracingTemporalHlsl, L"TemporalAccumCS");
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_COMPUTE_PIPELINE_STATE_DESC pso = {};
|
|
pso.pRootSignature = g_glRaytracingLighting.globalRootSig.Get();
|
|
pso.CS.pShaderBytecode = dxil->GetBufferPointer();
|
|
pso.CS.BytecodeLength = dxil->GetBufferSize();
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateComputePipelineState(
|
|
&pso,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.temporalPSO)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
|
|
{
|
|
if (width == 0 || height == 0)
|
|
return 0;
|
|
|
|
if (g_glRaytracingLighting.pathTraceTexture.resource &&
|
|
g_glRaytracingLighting.temporalTexture.resource &&
|
|
g_glRaytracingLighting.historyTexture[0].resource &&
|
|
g_glRaytracingLighting.historyTexture[1].resource &&
|
|
g_glRaytracingLighting.denoiseTemp[0].resource &&
|
|
g_glRaytracingLighting.denoiseTemp[1].resource &&
|
|
g_glRaytracingLighting.denoiseWidth == width &&
|
|
g_glRaytracingLighting.denoiseHeight == height &&
|
|
g_glRaytracingLighting.denoiseFormat == GL_RAYTRACING_DENOISE_FORMAT)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.pathTraceTexture.resource ||
|
|
g_glRaytracingLighting.temporalTexture.resource ||
|
|
g_glRaytracingLighting.historyTexture[0].resource ||
|
|
g_glRaytracingLighting.historyTexture[1].resource ||
|
|
g_glRaytracingLighting.denoiseTemp[0].resource ||
|
|
g_glRaytracingLighting.denoiseTemp[1].resource)
|
|
{
|
|
glRaytracingWaitIdle();
|
|
}
|
|
|
|
g_glRaytracingLighting.pathTraceTexture = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.temporalTexture = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.historyTexture[0] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.historyTexture[1] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.denoiseTemp[0] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.denoiseTemp[1] = glRaytracingTexture_t();
|
|
|
|
g_glRaytracingLighting.pathTraceTexture = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
g_glRaytracingLighting.temporalTexture = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
g_glRaytracingLighting.historyTexture[i] = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
}
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseTemp[i] = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
}
|
|
|
|
if (!g_glRaytracingLighting.pathTraceTexture.resource ||
|
|
!g_glRaytracingLighting.temporalTexture.resource ||
|
|
!g_glRaytracingLighting.historyTexture[0].resource ||
|
|
!g_glRaytracingLighting.historyTexture[1].resource ||
|
|
!g_glRaytracingLighting.denoiseTemp[0].resource ||
|
|
!g_glRaytracingLighting.denoiseTemp[1].resource)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
g_glRaytracingLighting.currentHistoryIndex = 0;
|
|
g_glRaytracingLighting.denoiseWidth = width;
|
|
g_glRaytracingLighting.denoiseHeight = height;
|
|
g_glRaytracingLighting.denoiseFormat = GL_RAYTRACING_DENOISE_FORMAT;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingCreatePerPassDescriptors(
|
|
const glRaytracingLightingPassDesc_t* pass,
|
|
ID3D12Resource* topLevelAS,
|
|
ID3D12Resource* rayOutputTexture,
|
|
ID3D12Resource* pathTraceTexture,
|
|
ID3D12Resource* denoiseATexture,
|
|
ID3D12Resource* denoiseBTexture,
|
|
ID3D12Resource* historyReadTexture,
|
|
ID3D12Resource* historyWriteTexture,
|
|
ID3D12Resource* temporalTexture)
|
|
{
|
|
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, GLR_DESC_ALBEDO_SRV));
|
|
|
|
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, GLR_DESC_DEPTH_SRV));
|
|
|
|
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, GLR_DESC_NORMAL_SRV));
|
|
|
|
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, GLR_DESC_POSITION_SRV));
|
|
|
|
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, GLR_DESC_TLAS_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC denoiseSrv = {};
|
|
denoiseSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
denoiseSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
denoiseSrv.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
denoiseSrv.Texture2D.MipLevels = 1;
|
|
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pathTraceTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(denoiseATexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_A_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(denoiseBTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_B_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(historyReadTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(temporalTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC emissiveSrv = {};
|
|
emissiveSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
emissiveSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
emissiveSrv.Format = g_glRaytracingLighting.emissiveFormat;
|
|
emissiveSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(g_glRaytracingLighting.emissiveTexture, &emissiveSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_EMISSIVE_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC specularSrv = {};
|
|
specularSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
specularSrv.Texture2D.MipLevels = 1;
|
|
ID3D12Resource* specularResource = g_glRaytracingLighting.specularTexture;
|
|
if (specularResource)
|
|
{
|
|
specularSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
specularSrv.Format = g_glRaytracingLighting.specularFormat;
|
|
}
|
|
else
|
|
{
|
|
// Bind a harmless fallback descriptor when the shim has not provided a
|
|
// specular G-buffer. The shader sees alpha zero and uses the legacy fallback.
|
|
specularResource = pass->albedoTexture;
|
|
specularSrv.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0);
|
|
specularSrv.Format = pass->albedoFormat;
|
|
}
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(specularResource, &specularSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_SPECULAR_SRV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {};
|
|
rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
rayOutputUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(rayOutputTexture, nullptr, &rayOutputUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC denoiseUav = {};
|
|
denoiseUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
denoiseUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(denoiseATexture, nullptr, &denoiseUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_A_UAV));
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(denoiseBTexture, nullptr, &denoiseUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_B_UAV));
|
|
|
|
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, GLR_DESC_OUTPUT_UAV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC temporalUav = {};
|
|
temporalUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
temporalUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(temporalTexture, nullptr, &temporalUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_UAV));
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(historyWriteTexture, nullptr, &temporalUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_UAV));
|
|
}
|
|
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
|
|
if (!glRaytracingLightingEnsureDenoiseResources(pass->width, pass->height))
|
|
return false;
|
|
|
|
const bool useInternalDenoiser =
|
|
(g_glRaytracingLighting.constants.enableDenoiser != 0u) &&
|
|
!g_glRaytracingLighting.externalDenoiser;
|
|
|
|
ID3D12Resource* rayOutputTexture = useInternalDenoiser
|
|
? g_glRaytracingLighting.pathTraceTexture.resource.Get()
|
|
: pass->outputTexture;
|
|
|
|
if (!glRaytracingBeginCmd())
|
|
return false;
|
|
|
|
glRaytracingLightingSelectFrameResources(g_glRaytracingCmd.cmdCurrentSlot);
|
|
|
|
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.frameIndex = g_glRaytracingLighting.frameCounter;
|
|
g_glRaytracingLighting.constants.lightCount =
|
|
(uint32_t)glRaytracingClamp<size_t>(g_glRaytracingLighting.cpuLights.size(), 0, GL_RAYTRACING_MAX_LIGHTS);
|
|
|
|
g_glRaytracingLighting.uploadToCurrentFrameResource = true;
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
|
|
for (uint32_t passIndex = 0; passIndex < 3u; ++passIndex)
|
|
{
|
|
glRaytracingLightingConstants_t denoiseConstants = g_glRaytracingLighting.constants;
|
|
denoiseConstants.denoisePassIndex = passIndex;
|
|
denoiseConstants.denoiseStepWidth = (float)(1u << passIndex);
|
|
glRaytracingLightingUploadConstantsTo(g_glRaytracingLighting.denoiseConstantBuffer[passIndex], denoiseConstants);
|
|
}
|
|
g_glRaytracingLighting.uploadToCurrentFrameResource = false;
|
|
|
|
const uint32_t historyReadIndex = g_glRaytracingLighting.currentHistoryIndex & 1u;
|
|
const uint32_t historyWriteIndex = (g_glRaytracingLighting.currentHistoryIndex ^ 1u) & 1u;
|
|
|
|
glRaytracingLightingCreatePerPassDescriptors(
|
|
pass,
|
|
topLevelAS,
|
|
rayOutputTexture,
|
|
g_glRaytracingLighting.pathTraceTexture.resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get());
|
|
|
|
if (useInternalDenoiser)
|
|
{
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.pathTraceTexture.resource.Get(),
|
|
g_glRaytracingLighting.pathTraceTexture.state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.pathTraceTexture.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
}
|
|
else
|
|
{
|
|
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, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA);
|
|
g_glRaytracingCmd.cmdList->SetPipelineState1(g_glRaytracingLighting.rtStateObject.Get());
|
|
|
|
const UINT timestampBase = g_glRaytracingCmd.cmdCurrentSlot * 2u;
|
|
const bool writeTimestamp =
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get() != nullptr &&
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource.Get() != nullptr;
|
|
if (writeTimestamp)
|
|
{
|
|
g_glRaytracingCmd.cmdList->EndQuery(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase + 0u);
|
|
}
|
|
|
|
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 * 2u;
|
|
rays.MissShaderTable.StrideInBytes = shaderRecordSize;
|
|
rays.HitGroupTable.StartAddress = g_glRaytracingLighting.hitTable.gpuVA;
|
|
rays.HitGroupTable.SizeInBytes = shaderRecordSize * 3u;
|
|
rays.HitGroupTable.StrideInBytes = shaderRecordSize;
|
|
rays.Width = pass->width;
|
|
rays.Height = pass->height;
|
|
rays.Depth = 1;
|
|
g_glRaytracingCmd.cmdList->DispatchRays(&rays);
|
|
|
|
D3D12_RESOURCE_BARRIER rawUav = {};
|
|
rawUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
rawUav.UAV.pResource = rayOutputTexture;
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &rawUav);
|
|
|
|
if (useInternalDenoiser)
|
|
{
|
|
const UINT groupsX = (pass->width + 7u) / 8u;
|
|
const UINT groupsY = (pass->height + 7u) / 8u;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.pathTraceTexture.resource.Get(),
|
|
g_glRaytracingLighting.pathTraceTexture.state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.pathTraceTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
|
|
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA);
|
|
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.temporalPSO.Get());
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
|
|
D3D12_RESOURCE_BARRIER temporalUavs[2] = {};
|
|
temporalUavs[0].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
temporalUavs[0].UAV.pResource = g_glRaytracingLighting.temporalTexture.resource.Get();
|
|
temporalUavs[1].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
temporalUavs[1].UAV.pResource = g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(2, temporalUavs);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[i].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[i].state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.denoiseTemp[i].state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
}
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pass->outputTexture,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
|
|
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.denoisePSO.Get());
|
|
|
|
// Pass 0: temporally accumulated path trace -> temp A.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[0].gpuVA);
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
|
|
D3D12_RESOURCE_BARRIER uavA = {};
|
|
uavA.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uavA.UAV.pResource = g_glRaytracingLighting.denoiseTemp[0].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &uavA);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.denoiseTemp[0].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
// Pass 1: temp A -> temp B.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[1].gpuVA);
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
|
|
D3D12_RESOURCE_BARRIER uavB = {};
|
|
uavB.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uavB.UAV.pResource = g_glRaytracingLighting.denoiseTemp[1].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &uavB);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.denoiseTemp[1].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
// Pass 2: temp B -> final output.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[2].gpuVA);
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
|
|
D3D12_RESOURCE_BARRIER outputUav = {};
|
|
outputUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
outputUav.UAV.pResource = pass->outputTexture;
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &outputUav);
|
|
}
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pass->outputTexture,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
|
|
|
if (writeTimestamp)
|
|
{
|
|
g_glRaytracingCmd.cmdList->EndQuery(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase + 1u);
|
|
g_glRaytracingCmd.cmdList->ResolveQueryData(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase,
|
|
2,
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource.Get(),
|
|
sizeof(UINT64) * timestampBase);
|
|
}
|
|
|
|
if (!glRaytracingEndCmd())
|
|
return false;
|
|
|
|
if (writeTimestamp)
|
|
g_glRaytracingCmd.pathTracingTimestampFenceRing[g_glRaytracingCmd.cmdCurrentSlot] = g_glRaytracingCmd.cmdLastFenceValue;
|
|
|
|
if (useInternalDenoiser)
|
|
g_glRaytracingLighting.currentHistoryIndex = historyWriteIndex;
|
|
|
|
++g_glRaytracingLighting.frameCounter;
|
|
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<std::mutex> 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;
|
|
|
|
if (!glRaytracingLightingCreateDenoisePipeline())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateTemporalPipeline())
|
|
return false;
|
|
|
|
memset(&g_glRaytracingLighting.constants, 0, sizeof(g_glRaytracingLighting.constants));
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewProj);
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewMatrix);
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj);
|
|
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;
|
|
g_glRaytracingLighting.constants.frameIndex = 0;
|
|
g_glRaytracingLighting.constants.samplesPerPixel = 1;
|
|
g_glRaytracingLighting.constants.maxBounces = 2;
|
|
g_glRaytracingLighting.constants.enableDenoiser = 1;
|
|
g_glRaytracingLighting.constants.denoisePassIndex = 0;
|
|
g_glRaytracingLighting.constants.denoiseStepWidth = 1.0f;
|
|
g_glRaytracingLighting.constants.denoiseStrength = 1.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiColor = 8.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiNormal = 64.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiPosition = 0.045f;
|
|
g_glRaytracingLighting.constants.bumpStrength = 2.35f;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
glRaytracingLightingUpdateConstants();
|
|
|
|
g_glRaytracingLighting.initialized = true;
|
|
glRaytracingLog("glRaytracingLightingInit ok");
|
|
return true;
|
|
}
|
|
|
|
void glRaytracingLightingShutdown(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingLighting.initialized)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingLightingUnmapUploadBuffers();
|
|
g_glRaytracingLighting = glRaytracingLightingState_t();
|
|
}
|
|
|
|
bool glRaytracingLightingIsInitialized(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
return g_glRaytracingLighting.initialized;
|
|
}
|
|
|
|
void glRaytracingLightingClearLights(bool clearPersistant)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
glRaytracingLightingEnsureLightKeyCount();
|
|
|
|
if (clearPersistant)
|
|
{
|
|
g_glRaytracingLighting.cpuLights.clear();
|
|
g_glRaytracingLighting.cpuLightKeys.clear();
|
|
}
|
|
else
|
|
{
|
|
size_t writeIndex = 0;
|
|
|
|
for (size_t i = 0; i < g_glRaytracingLighting.cpuLights.size(); ++i)
|
|
{
|
|
const uint32_t lightKey = g_glRaytracingLighting.cpuLightKeys[i];
|
|
|
|
// Preserve normal persistent lights and renderer-managed geometry emitters.
|
|
// This prevents the depth-fill emissive bridge from deleting ordinary
|
|
// non-persistent point/spot lights by accident, and it keeps discovered
|
|
// geometry emitters alive when their source surface is off-screen.
|
|
if (g_glRaytracingLighting.cpuLights[i].persistant != 0.0f ||
|
|
glRaytracingLightKeyIsGeometryEmissive(lightKey))
|
|
{
|
|
if (writeIndex != i)
|
|
{
|
|
g_glRaytracingLighting.cpuLights[writeIndex] = g_glRaytracingLighting.cpuLights[i];
|
|
g_glRaytracingLighting.cpuLightKeys[writeIndex] = lightKey;
|
|
}
|
|
++writeIndex;
|
|
}
|
|
}
|
|
|
|
g_glRaytracingLighting.cpuLights.resize(writeIndex);
|
|
g_glRaytracingLighting.cpuLightKeys.resize(writeIndex);
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.lightCount =
|
|
(uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
|
|
if (clearPersistant)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
bool glRaytracingLightingAddLight(const glRaytracingLight_t* light)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingLighting.initialized || !light)
|
|
return false;
|
|
|
|
glRaytracingLightingEnsureLightKeyCount();
|
|
|
|
if (g_glRaytracingLighting.cpuLights.size() >= GL_RAYTRACING_MAX_LIGHTS)
|
|
return false;
|
|
|
|
const bool persistentLight = (light->persistant > 0.0f);
|
|
|
|
g_glRaytracingLighting.cpuLights.push_back(*light);
|
|
g_glRaytracingLighting.cpuLightKeys.push_back(GL_RAYTRACING_LIGHT_KEY_NONE);
|
|
g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
|
|
// Positive persistent/static lights invalidate accumulation. Negative
|
|
// persist tags are renderer-managed metadata and must not reset every frame.
|
|
if (persistentLight)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
return true;
|
|
}
|
|
|
|
bool glRaytracingLightingAddOrUpdateGeometryEmissiveLight(uint32_t key, const glRaytracingLight_t* light)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingLighting.initialized || !light)
|
|
return false;
|
|
|
|
glRaytracingLightingEnsureLightKeyCount();
|
|
|
|
const uint32_t geometryKey = glRaytracingMakeGeometryEmissiveLightKey(key);
|
|
glRaytracingLight_t taggedLight = *light;
|
|
taggedLight.persistant = GL_RAYTRACING_LIGHT_PERSISTANT_GEOMETRY_EMISSIVE;
|
|
|
|
for (size_t i = 0; i < g_glRaytracingLighting.cpuLightKeys.size(); ++i)
|
|
{
|
|
if (g_glRaytracingLighting.cpuLightKeys[i] != geometryKey)
|
|
continue;
|
|
|
|
if (memcmp(&g_glRaytracingLighting.cpuLights[i], &taggedLight, sizeof(taggedLight)) != 0)
|
|
{
|
|
g_glRaytracingLighting.cpuLights[i] = taggedLight;
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.cpuLights.size() >= GL_RAYTRACING_MAX_LIGHTS)
|
|
return false;
|
|
|
|
g_glRaytracingLighting.cpuLights.push_back(taggedLight);
|
|
g_glRaytracingLighting.cpuLightKeys.push_back(geometryKey);
|
|
g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
|
|
// Geometry emitters are discovered from draw surfaces. Do not reset denoise
|
|
// history when they are first seen or re-seen; otherwise camera movement and
|
|
// PVS changes keep the path tracer permanently noisy.
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
return true;
|
|
}
|
|
|
|
void glRaytracingLightingClearGeometryEmissiveLights(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
glRaytracingLightingEnsureLightKeyCount();
|
|
|
|
size_t writeIndex = 0;
|
|
for (size_t i = 0; i < g_glRaytracingLighting.cpuLights.size(); ++i)
|
|
{
|
|
const uint32_t lightKey = g_glRaytracingLighting.cpuLightKeys[i];
|
|
if (glRaytracingLightKeyIsGeometryEmissive(lightKey))
|
|
continue;
|
|
|
|
if (writeIndex != i)
|
|
{
|
|
g_glRaytracingLighting.cpuLights[writeIndex] = g_glRaytracingLighting.cpuLights[i];
|
|
g_glRaytracingLighting.cpuLightKeys[writeIndex] = lightKey;
|
|
}
|
|
++writeIndex;
|
|
}
|
|
|
|
g_glRaytracingLighting.cpuLights.resize(writeIndex);
|
|
g_glRaytracingLighting.cpuLightKeys.resize(writeIndex);
|
|
g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetAmbient(float r, float g, float b, float intensity)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.ambientColor[0] != r ||
|
|
g_glRaytracingLighting.constants.ambientColor[1] != g ||
|
|
g_glRaytracingLighting.constants.ambientColor[2] != b ||
|
|
g_glRaytracingLighting.constants.ambientColor[3] != intensity)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
// Camera position changes every frame while the player moves. Treat it as a
|
|
// normal frame constant, not a light/material change, or the temporal GI
|
|
// accumulator is reset exactly when the image needs it most.
|
|
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<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!m16)
|
|
return;
|
|
|
|
memcpy(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16);
|
|
|
|
if (!glRaytracingInvertMatrix4x4(m16, g_glRaytracingLighting.constants.viewProj))
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj);
|
|
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetInvViewMatrix(const float* m16)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!m16)
|
|
return;
|
|
|
|
memcpy(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16);
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetNormalReconstructSign(float signValue)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.normalReconstructZ != signValue)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.normalReconstructZ = signValue;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetBumpStrength(float strength)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
strength = glRaytracingClamp<float>(strength, 0.0f, 8.0f);
|
|
if (g_glRaytracingLighting.constants.bumpStrength != strength)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.bumpStrength = strength;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingEnableSpecular(int enable)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t value = enable ? 1u : 0u;
|
|
if (g_glRaytracingLighting.constants.enableSpecular != value)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.enableSpecular = value;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingEnableHalfLambert(int enable)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t value = enable ? 1u : 0u;
|
|
if (g_glRaytracingLighting.constants.enableHalfLambert != value)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.enableHalfLambert = value;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetShadowBias(float bias)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.shadowBias != bias)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.shadowBias = bias;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_t maxBounces, int enableDenoiser, float denoiseStrength)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
samplesPerPixel = glRaytracingClamp<uint32_t>(samplesPerPixel ? samplesPerPixel : 1u, 1u, 8u);
|
|
maxBounces = glRaytracingClamp<uint32_t>(maxBounces ? maxBounces : 1u, 1u, 4u);
|
|
uint32_t denoiser = enableDenoiser ? 1u : 0u;
|
|
denoiseStrength = glRaytracingClamp<float>(denoiseStrength, 0.0f, 1.0f);
|
|
|
|
if (g_glRaytracingLighting.constants.samplesPerPixel != samplesPerPixel ||
|
|
g_glRaytracingLighting.constants.maxBounces != maxBounces ||
|
|
g_glRaytracingLighting.constants.enableDenoiser != denoiser ||
|
|
g_glRaytracingLighting.constants.denoiseStrength != denoiseStrength)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.samplesPerPixel = samplesPerPixel;
|
|
g_glRaytracingLighting.constants.maxBounces = maxBounces;
|
|
g_glRaytracingLighting.constants.enableDenoiser = denoiser;
|
|
g_glRaytracingLighting.constants.denoiseStrength = denoiseStrength;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetDenoiseTuning(float phiColor, float phiNormal, float phiPosition)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
phiColor = glRaytracingClamp<float>(phiColor, 0.001f, 64.0f);
|
|
phiNormal = glRaytracingClamp<float>(phiNormal, 1.0f, 128.0f);
|
|
phiPosition = glRaytracingClamp<float>(phiPosition, 0.001f, 4.0f);
|
|
|
|
if (g_glRaytracingLighting.constants.denoisePhiColor != phiColor ||
|
|
g_glRaytracingLighting.constants.denoisePhiNormal != phiNormal ||
|
|
g_glRaytracingLighting.constants.denoisePhiPosition != phiPosition)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.denoisePhiColor = phiColor;
|
|
g_glRaytracingLighting.constants.denoisePhiNormal = phiNormal;
|
|
g_glRaytracingLighting.constants.denoisePhiPosition = phiPosition;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength)
|
|
{
|
|
if (!light)
|
|
return;
|
|
|
|
// This uses glRaytracingLight_t::pad1, which is renamed to
|
|
// Light::volumetricScattering in HLSL. Keeping the existing pad slot avoids
|
|
// changing the StructuredBuffer stride for already-integrated callers.
|
|
light->pad1 = glRaytracingClamp<float>(strength, 0.0f, 16.0f);
|
|
}
|
|
|
|
void glRaytracingLightingSetExternalDenoiser(int enabled)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const bool newValue = enabled ? true : false;
|
|
if (g_glRaytracingLighting.externalDenoiser != newValue)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.externalDenoiser = newValue;
|
|
}
|
|
|
|
void glRaytracingLightingResetTemporalHistory(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingUseExternalDenoiser(int enabled)
|
|
{
|
|
glRaytracingLightingSetExternalDenoiser(enabled);
|
|
}
|
|
|
|
|
|
void glRaytracingLightingSetEmissiveInput(ID3D12Resource* texture, DXGI_FORMAT format)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
g_glRaytracingLighting.emissiveTexture = texture;
|
|
g_glRaytracingLighting.emissiveFormat = (format == DXGI_FORMAT_UNKNOWN)
|
|
? DXGI_FORMAT_R16G16B16A16_FLOAT
|
|
: format;
|
|
}
|
|
|
|
void glRaytracingLightingSetSpecularInput(ID3D12Resource* texture, DXGI_FORMAT format)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
DXGI_FORMAT newFormat = (format == DXGI_FORMAT_UNKNOWN)
|
|
? DXGI_FORMAT_R8G8B8A8_UNORM
|
|
: format;
|
|
|
|
// The G-buffer specular resource is normally double-buffered, so the texture
|
|
// pointer can change every frame even when the material data is stable. Do not
|
|
// reset temporal GI just because the current frame resource rotated; only a
|
|
// real format change invalidates the accumulator.
|
|
if (g_glRaytracingLighting.specularFormat != newFormat)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.specularTexture = texture;
|
|
g_glRaytracingLighting.specularFormat = newFormat;
|
|
}
|
|
|
|
bool glRaytracingLightingExecuteForScene(const glRaytracingLightingPassDesc_t* pass, glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> 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.falloffPower = 1.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; // volumetric scattering disabled by default.
|
|
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.falloffPower = 1.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; // volumetric scattering disabled by default.
|
|
|
|
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.falloffPower = 1.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 : 16u;
|
|
if (l.samples > 16u) l.samples = 16u;
|
|
l.twoSided = twoSided ? 1u : 0u;
|
|
l.persistant = 0.0f;
|
|
l.pad1 = 0.0f; // volumetric scattering disabled by default.
|
|
|
|
return l;
|
|
}
|
|
|
|
uint32_t glRaytracingLightingGetLightCount(void)
|
|
{
|
|
return (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
}
|
|
|
|
int glRaytracingSetInstanceVisibilityUnlocked(
|
|
glRaytracingRenderWorld_t* world,
|
|
glRaytracingInstanceHandle_t instanceHandle,
|
|
int visible)
|
|
{
|
|
if (!world || !world->alive)
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t* inst =
|
|
glRaytracingFindInstance(world, instanceHandle);
|
|
|
|
if (!inst || !inst->alive)
|
|
return 0;
|
|
|
|
const uint32_t newMask = visible ? 0xFFu : 0u;
|
|
|
|
if ((((uint32_t)inst->descCpu.mask) & 0xFFu) == newMask)
|
|
return 1;
|
|
|
|
inst->descCpu.mask = newMask;
|
|
inst->dirty = 1;
|
|
|
|
glRaytracingMarkWorldNeedsUpdate(world);
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingSetAllInstancesVisibleUnlocked(
|
|
glRaytracingRenderWorld_t* world,
|
|
int visible)
|
|
{
|
|
if (!world || !world->alive)
|
|
return;
|
|
|
|
const uint32_t newMask = visible ? 0xFFu : 0u;
|
|
int changed = 0;
|
|
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
glRaytracingInstanceRecord_t& inst = world->instances[i];
|
|
|
|
if (!inst.alive)
|
|
continue;
|
|
|
|
const int wasVisible = inst.descCpu.mask != 0;
|
|
if (wasVisible == (visible != 0))
|
|
continue;
|
|
|
|
inst.descCpu.mask = newMask;
|
|
inst.dirty = 1;
|
|
changed = 1;
|
|
}
|
|
|
|
if (changed)
|
|
glRaytracingMarkWorldNeedsUpdate(world);
|
|
}
|
|
|
|
int glRaytracingSetInstanceVisibilityInScene(
|
|
glRaytracingSceneHandle_t sceneHandle,
|
|
glRaytracingInstanceHandle_t instanceHandle,
|
|
int visible)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
return glRaytracingSetInstanceVisibilityUnlocked(
|
|
world,
|
|
instanceHandle,
|
|
visible ? 1 : 0);
|
|
}
|
|
|
|
int glRaytracingGetInstanceVisibilityInScene(
|
|
glRaytracingSceneHandle_t sceneHandle,
|
|
glRaytracingInstanceHandle_t instanceHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
if (!world)
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t* inst =
|
|
glRaytracingFindInstance(world, instanceHandle);
|
|
|
|
if (!inst || !inst->alive)
|
|
return 0;
|
|
|
|
return inst->descCpu.mask != 0 ? 1 : 0;
|
|
}
|
|
|
|
void glRaytracingHideAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
glRaytracingSetAllInstancesVisibleUnlocked(world, 0);
|
|
}
|
|
|
|
void glRaytracingShowAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
glRaytracingSetAllInstancesVisibleUnlocked(world, 1);
|
|
}
|
|
|
|
void glRaytracingHideAllInstances(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 0);
|
|
}
|
|
|
|
void glRaytracingShowAllInstances(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 1);
|
|
}
|