diff --git a/base/ies/spot1.ies b/base/ies/spot1.ies new file mode 100644 index 00000000..60738a51 --- /dev/null +++ b/base/ies/spot1.ies @@ -0,0 +1,64 @@ +ERCO Leuchten GmbH +3399_0 - IES format +ERCO 89064 +ERCO Double Focus Downlight +DL40-QT12-2HA-SI-DOUBLE +1*QT-ax12 100W/12V +light output ratio: 0.5912 +ident no. for symmetry: 1 +D694 +TILT=NONE + 1 + 2200 + 2.2 + 19 + 1 + 1 + 2 + 0 + 0 + 0 + 1 + 1 + 100 + 0 + 5 + 10 + 15 + 20 + 25 + 30 + 35 + 40 + 45 + 50 + 55 + 60 + 65 + 70 + 75 + 80 + 85 + 90 + 0 + 681.879276637 + 557.110079287 + 490.896871945 + 628.387368307 + 682.069349408 + 430.426577604 + 286.398935592 + 254.901162159 + 76.9183773216 + 18.9393939394 + 11.0377973281 + .488758553275 + .00678831323993 + 0 + 0 + 0 + 0 + 0 + 0 +END + \ No newline at end of file diff --git a/base/materials/ies.mtr b/base/materials/ies.mtr new file mode 100644 index 00000000..41043725 --- /dev/null +++ b/base/materials/ies.mtr @@ -0,0 +1,3 @@ +lights/iesspotlight { + lightIESTexture ies/spot1.ies +} \ No newline at end of file diff --git a/neo/doom3/game/gamesys/GameTypeInfo.h b/neo/doom3/game/gamesys/GameTypeInfo.h index 42a10305..8791ee5f 100644 --- a/neo/doom3/game/gamesys/GameTypeInfo.h +++ b/neo/doom3/game/gamesys/GameTypeInfo.h @@ -4058,6 +4058,9 @@ static classVariableInfo_t glRaytracingLight_t_typeInfo[] = { { "float", "persistant", (intptr_t)(&((glRaytracingLight_t *)0)->persistant), sizeof( ((glRaytracingLight_t *)0)->persistant ) }, { "glRaytracingVec3_t", "pointRadius", (intptr_t)(&((glRaytracingLight_t *)0)->pointRadius), sizeof( ((glRaytracingLight_t *)0)->pointRadius ) }, { "float", "pointRadiusPad", (intptr_t)(&((glRaytracingLight_t *)0)->pointRadiusPad), sizeof( ((glRaytracingLight_t *)0)->pointRadiusPad ) }, + { "uint32_t", "iesTextureId", (intptr_t)(&((glRaytracingLight_t *)0)->iesTextureId), sizeof( ((glRaytracingLight_t *)0)->iesTextureId ) }, + { "float", "iesStrength", (intptr_t)(&((glRaytracingLight_t *)0)->iesStrength), sizeof( ((glRaytracingLight_t *)0)->iesStrength ) }, + { "float[2]", "iesPad", (intptr_t)(&((glRaytracingLight_t *)0)->iesPad), sizeof( ((glRaytracingLight_t *)0)->iesPad ) }, { NULL, 0 } }; @@ -4218,6 +4221,7 @@ static classVariableInfo_t idMaterial_typeInfo[] = { { "idStr", "desc", (intptr_t)(&((idMaterial *)0)->desc), sizeof( ((idMaterial *)0)->desc ) }, { "idStr", "renderBump", (intptr_t)(&((idMaterial *)0)->renderBump), sizeof( ((idMaterial *)0)->renderBump ) }, { "idImage *", "lightFalloffImage", (intptr_t)(&((idMaterial *)0)->lightFalloffImage), sizeof( ((idMaterial *)0)->lightFalloffImage ) }, + { "idImage *", "lightIESImage", (intptr_t)(&((idMaterial *)0)->lightIESImage), sizeof( ((idMaterial *)0)->lightIESImage ) }, { "int", "entityGui", (intptr_t)(&((idMaterial *)0)->entityGui), sizeof( ((idMaterial *)0)->entityGui ) }, { "mutable idUserInterface *", "gui", (intptr_t)(&((idMaterial *)0)->gui), sizeof( ((idMaterial *)0)->gui ) }, { "bool", "noFog", (intptr_t)(&((idMaterial *)0)->noFog), sizeof( ((idMaterial *)0)->noFog ) }, diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index 50d95d15..650f580a 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -2469,9 +2469,12 @@ struct glRaytracingMeshMetadata_t float averageColor[4]; }; +static const uint32_t GL_RAYTRACING_MAX_IES_TEXTURES = 8; + struct glRaytracingLightingState_t { std::vector cpuLights; + std::vector uploadLights; glRaytracingLightingConstants_t constants; ComPtr descriptorHeap; @@ -2522,6 +2525,9 @@ struct glRaytracingLightingState_t DXGI_FORMAT emissiveFormat; ID3D12Resource* specularTexture; DXGI_FORMAT specularFormat; + uint32_t iesTextureIds[GL_RAYTRACING_MAX_IES_TEXTURES]; + ID3D12Resource* iesTextures[GL_RAYTRACING_MAX_IES_TEXTURES]; + DXGI_FORMAT iesFormats[GL_RAYTRACING_MAX_IES_TEXTURES]; bool uploadToCurrentFrameResource; bool initialized; @@ -2553,6 +2559,12 @@ struct glRaytracingLightingState_t emissiveFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; specularTexture = nullptr; specularFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + for (uint32_t i = 0; i < GL_RAYTRACING_MAX_IES_TEXTURES; ++i) + { + iesTextureIds[i] = 0; + iesTextures[i] = nullptr; + iesFormats[i] = DXGI_FORMAT_R8G8B8A8_UNORM; + } uploadToCurrentFrameResource = false; initialized = false; } @@ -2577,14 +2589,15 @@ enum glRaytracingLightingDescriptorIndex_t GLR_DESC_EMISSIVE_SRV = 11, GLR_DESC_SPECULAR_SRV = 12, GLR_DESC_MESH_METADATA_SRV = 13, - GLR_DESC_PATHTRACE_UAV = 14, - GLR_DESC_DENOISE_A_UAV = 15, - GLR_DESC_DENOISE_B_UAV = 16, - GLR_DESC_OUTPUT_UAV = 17, - GLR_DESC_TEMPORAL_UAV = 18, - GLR_DESC_HISTORY_UAV = 19, - GLR_DESC_COUNT = 20, - GLR_DESC_SRV_COUNT = 14, + GLR_DESC_IES_TEXTURES_SRV = 14, + GLR_DESC_PATHTRACE_UAV = GLR_DESC_IES_TEXTURES_SRV + GL_RAYTRACING_MAX_IES_TEXTURES, + GLR_DESC_DENOISE_A_UAV = GLR_DESC_PATHTRACE_UAV + 1, + GLR_DESC_DENOISE_B_UAV = GLR_DESC_PATHTRACE_UAV + 2, + GLR_DESC_OUTPUT_UAV = GLR_DESC_PATHTRACE_UAV + 3, + GLR_DESC_TEMPORAL_UAV = GLR_DESC_PATHTRACE_UAV + 4, + GLR_DESC_HISTORY_UAV = GLR_DESC_PATHTRACE_UAV + 5, + GLR_DESC_COUNT = GLR_DESC_PATHTRACE_UAV + 6, + GLR_DESC_SRV_COUNT = 14 + GL_RAYTRACING_MAX_IES_TEXTURES, GLR_DESC_UAV_COUNT = 6 }; @@ -2631,6 +2644,10 @@ struct Light // 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 + + uint iesTextureIndex; // 0 = none, 1..8 = gIesTextures index + 1 + float iesStrength; + float2 iesPad; }; struct MeshMetadata @@ -2686,6 +2703,7 @@ RaytracingAccelerationStructure gSceneBVH : register(t5); Texture2D gEmissiveTex : register(t11); Texture2D gSpecularTex : register(t12); StructuredBuffer gMeshMetadata : register(t13); +Texture2D gIesTextures[8] : register(t14); RWTexture2D gOutputTex : register(u0); static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0; @@ -3143,6 +3161,60 @@ float Doom3ProjectionTexture2D(float2 centeredCoord) return Doom3QuadraticCentered(centeredCoord.x) * Doom3QuadraticCentered(centeredCoord.y); } + +int WrapIESHorizontalCoord(int x) +{ + x = x % 512; + return (x < 0) ? (x + 512) : x; +} + +float SampleIESTexture(Light Lgt, float3 lightToSurface) +{ + if (Lgt.iesTextureIndex == 0u || Lgt.iesStrength <= 0.0) + return 1.0; + + float3 dir = Doom3SafeNormalizeOr(lightToSurface, Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0))); + float3 axisW = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.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 localX = dot(dir, axisU); + float localY = dot(dir, axisV); + float localZ = dot(dir, axisW); + + float horizontal = atan2(localY, localX) * (0.15915494309189535) + 0.5; + float vertical = acos(clamp(localZ, -1.0, 1.0)) * (0.3183098861837907); + + uint texIndex = min(Lgt.iesTextureIndex - 1u, 7u); + float u = frac(horizontal) * 512.0 - 0.5; + float v = saturate(vertical) * 256.0 - 0.5; + int x0 = (int)floor(u); + int y0 = (int)floor(v); + float tx = frac(u); + float ty = frac(v); + int x1 = x0 + 1; + int y1 = y0 + 1; + + x0 = WrapIESHorizontalCoord(x0); + x1 = WrapIESHorizontalCoord(x1); + y0 = clamp(y0, 0, 255); + y1 = clamp(y1, 0, 255); + + float p00 = gIesTextures[texIndex].Load(int3(x0, y0, 0)).r; + float p10 = gIesTextures[texIndex].Load(int3(x1, y0, 0)).r; + float p01 = gIesTextures[texIndex].Load(int3(x0, y1, 0)).r; + float p11 = gIesTextures[texIndex].Load(int3(x1, y1, 0)).r; + float profile = lerp(lerp(p00, p10, tx), lerp(p01, p11, tx), ty); + return lerp(1.0, profile, saturate(Lgt.iesStrength)); +} + +float ApplyIESProfile(float attenuation, Light Lgt, float3 lightToSurface) +{ + if (attenuation <= 0.0 || Lgt.iesTextureIndex == 0u || Lgt.iesStrength <= 0.0) + return attenuation; + + return attenuation * SampleIESTexture(Lgt, lightToSurface); +} )" R"( float Doom3ProjectedDepthFalloff(float depth, float nearClip, float farClip) @@ -3192,6 +3264,15 @@ float ComputeSpotLightAttenuation(float3 worldPos, Light Lgt) float depth = dot(lightToSurface, spotDir); + if (Lgt.iesTextureIndex != 0u && Lgt.iesStrength > 0.0) + { + if (depth <= 1e-4 || depth >= farClip) + return 0.0; + + float t = saturate(depth / max(farClip, 1e-4)); + return 1.0 - smoothstep(0.985, 1.0, t); + } + if (depth <= nearClip || depth >= farClip) return 0.0; @@ -4023,7 +4104,7 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 float3 tangent, bitangent; BuildOrthonormalBasis(centerDir, tangent, bitangent); - float atten = ComputePointLightAttenuation(worldPos, Lgt); + float atten = ApplyIESProfile(ComputePointLightAttenuation(worldPos, Lgt), Lgt, worldPos - Lgt.position); if (atten <= 0.0) return 0.0; @@ -4081,7 +4162,7 @@ float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 base return 0.0; float3 L = toLight / dist; - float atten = ComputeSpotLightAttenuation(worldPos, Lgt); + float atten = ApplyIESProfile(ComputeSpotLightAttenuation(worldPos, Lgt), Lgt, worldPos - Lgt.position); float diffuseNoL = ComputeDiffuseLightingTerm(N, L); @@ -5264,7 +5345,7 @@ void RayGen() reflectionRng); float3 albedo = baseAlbedo * cavity; - float3 finalColor = (albedo * (lightingAccum + float3(0.08, 0.08, 0.08))) + specularAccum + reflectedSpecular; + float3 finalColor = (albedo * (lightingAccum)) + specularAccum + reflectedSpecular; if (gMaxBounces > 1u) { @@ -6073,16 +6154,72 @@ static void glRaytracingLightingUpdateLights(void) if (g_glRaytracingLighting.cpuLights.empty()) return; - const size_t bytes = g_glRaytracingLighting.cpuLights.size() * sizeof(glRaytracingLight_t); + std::vector& uploadLights = g_glRaytracingLighting.uploadLights; + uploadLights = g_glRaytracingLighting.cpuLights; + + for (size_t i = 0; i < uploadLights.size(); ++i) + { + glRaytracingLight_t& light = uploadLights[i]; + const uint32_t textureId = light.iesTextureId; + if (textureId == 0 || light.iesStrength <= 0.0f) + { + light.iesTextureId = 0; + light.iesStrength = 0.0f; + continue; + } + + uint32_t slot = GL_RAYTRACING_MAX_IES_TEXTURES; + for (uint32_t j = 0; j < GL_RAYTRACING_MAX_IES_TEXTURES; ++j) + { + if (g_glRaytracingLighting.iesTextureIds[j] == textureId) + { + slot = j; + break; + } + if (g_glRaytracingLighting.iesTextureIds[j] == 0 && slot == GL_RAYTRACING_MAX_IES_TEXTURES) + { + slot = j; + } + } + + if (slot == GL_RAYTRACING_MAX_IES_TEXTURES) + { + light.iesTextureId = 0; + light.iesStrength = 0.0f; + continue; + } + + if (g_glRaytracingLighting.iesTextureIds[slot] == 0) + { + DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM; + UINT width = 0; + UINT height = 0; + ID3D12Resource* resource = QD3D12_GetTextureResource((GLuint)textureId, &format, &width, &height, g_glRaytracingCmd.cmdList.Get()); + if (!resource || width == 0 || height == 0) + { + light.iesTextureId = 0; + light.iesStrength = 0.0f; + continue; + } + + g_glRaytracingLighting.iesTextureIds[slot] = textureId; + g_glRaytracingLighting.iesTextures[slot] = resource; + g_glRaytracingLighting.iesFormats[slot] = format; + } + + light.iesTextureId = slot + 1; + } + + const size_t bytes = uploadLights.size() * sizeof(glRaytracingLight_t); if (g_glRaytracingLighting.lightBufferMapped) { - memcpy(g_glRaytracingLighting.lightBufferMapped, g_glRaytracingLighting.cpuLights.data(), bytes); + memcpy(g_glRaytracingLighting.lightBufferMapped, uploadLights.data(), bytes); return; } glRaytracingMapCopy( g_glRaytracingLighting.lightBuffer.resource.Get(), - g_glRaytracingLighting.cpuLights.data(), + uploadLights.data(), bytes); } @@ -6622,6 +6759,21 @@ static void glRaytracingLightingCreatePerPassDescriptors( g_glRaytracingCmd.device->CreateShaderResourceView(specularResource, &specularSrv, glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_SPECULAR_SRV)); + for (uint32_t i = 0; i < GL_RAYTRACING_MAX_IES_TEXTURES; ++i) + { + D3D12_SHADER_RESOURCE_VIEW_DESC iesSrv = {}; + iesSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + iesSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + iesSrv.Format = g_glRaytracingLighting.iesTextures[i] + ? g_glRaytracingLighting.iesFormats[i] + : DXGI_FORMAT_R8G8B8A8_UNORM; + iesSrv.Texture2D.MipLevels = 1; + g_glRaytracingCmd.device->CreateShaderResourceView( + g_glRaytracingLighting.iesTextures[i], + &iesSrv, + glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_IES_TEXTURES_SRV + i)); + } + D3D12_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {}; rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; rayOutputUav.Format = GL_RAYTRACING_DENOISE_FORMAT; diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index ca6d2799..9337330c 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -373,6 +373,7 @@ void QD3D12_SetCameraInfo( static void QD3D12_CreateUploadRingForWindow(struct QD3D12Window& w); static void QD3D12_DestroyUploadRingForWindow(struct QD3D12Window& w); static void QD3D12_SubmitOpenFrameNoPresentAndWait(); +static void QD3D12_WaitForGPU(); static void QD3D12_EnsureFrameOpen(); static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w); static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w); @@ -664,7 +665,7 @@ static void QD3D12_InvalidateTextureMipChain(TextureResource& tex, bool invalida static bool QD3D12_TextureHasNeuralPOMData(const TextureResource& tex); static bool QD3D12_UploadNeuralPOM(TextureResource& tex); static void EnsureTextureResource(TextureResource& tex); -static void UploadTexture(TextureResource& tex); +static void UploadTexture(TextureResource& tex, ID3D12GraphicsCommandList* commandList = nullptr); static UINT QD3D12_NeuralPOMFallbackSrvIndex(); static void QD3D12_CreateNeuralPOMZeroBuffer(); @@ -1336,17 +1337,6 @@ struct QD3D12Window* AllocD3D12Window() { return new QD3D12Window(); } -void FreeD3D12Window(struct QD3D12Window* wnd) { - if (wnd) - { - QD3D12_DestroyUploadRingForWindow(*wnd); - QD3D12_UnregisterWindowDC(wnd->hdc); - if (wnd->ownsHdc && wnd->hwnd && wnd->hdc) - ReleaseDC(wnd->hwnd, wnd->hdc); - } - delete wnd; -} - struct ImmediateVertexBuffer { std::vector storage; @@ -1514,7 +1504,7 @@ struct GLState bool enableRayAIDenoise = false; bool enableDLSSRayReconstruction = false; bool enableFSRRayRegeneration = false; - uint32_t frameGenerationMultiplier = 2; + uint32_t frameGenerationMultiplier = 0; uint32_t pathTracingSamplesPerPixel = 1; uint32_t pathTracingFallbackSamplesPerPixel = 2; uint32_t pathTracingMaxBounces = 2; @@ -1850,6 +1840,24 @@ static void QD3D12_MatrixMultiplyCM(const float* a, const float* b, float* out) memcpy(out, r, sizeof(r)); } +void FreeD3D12Window(struct QD3D12Window* wnd) { + if (wnd) + { + if ((g_gl.frameOpen && g_gl.frameOwner == wnd) || g_currentWindow == wnd) + QD3D12_SubmitOpenFrameNoPresentAndWait(); + if (g_currentWindow == wnd) + g_currentWindow = nullptr; + if (g_gl.device && g_gl.queue && g_gl.fence) + QD3D12_WaitForGPU(); + QD3D12_DestroyUploadRingForWindow(*wnd); + QD3D12_UnregisterWindowDC(wnd->hdc); + if (wnd->ownsHdc && wnd->hwnd && wnd->hdc) + ReleaseDC(wnd->hwnd, wnd->hdc); + } + delete wnd; +} + + static bool QD3D12_MatrixInvertCM(const float* m, float* out) { if (!QD3D12_MatrixFinite(m) || !out) @@ -2583,6 +2591,32 @@ static TextureResource* QD3D12_FindTextureResource(GLuint id) return &it->second; } +ID3D12Resource* QD3D12_GetTextureResource(GLuint texture, DXGI_FORMAT* format, UINT* width, UINT* height, ID3D12GraphicsCommandList* commandList) +{ + TextureResource* tex = QD3D12_FindTextureResource(texture); + if (!tex) + return nullptr; + + EnsureTextureResource(*tex); + if (!tex->texture) + return nullptr; + + if (!tex->gpuValid && commandList) + UploadTexture(*tex, commandList); + + if (!tex->gpuValid) + return nullptr; + + if (format) + *format = tex->dxgiFormat; + if (width) + *width = (UINT)tex->width; + if (height) + *height = (UINT)tex->height; + + return tex->texture.Get(); +} + static TextureResource& QD3D12_EnsureTextureName(GLuint id) { auto it = g_gl.textures.find(id); @@ -6842,6 +6876,11 @@ static float QD3D12_QualityRatio(QD3D12UpscalerQuality quality) } } +static bool QD3D12_IsEmbeddedEditorWindow(const QD3D12Window& w) +{ + return w.hwnd && GetParent(w.hwnd) != nullptr; +} + #if defined(QD3D12_ENABLE_STREAMLINE) struct QD3D12StreamlineState { @@ -7169,6 +7208,7 @@ static bool QD3D12_WantsDLSSRayReconstruction() static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window& w) { return !w.isPbuffer && + !QD3D12_IsEmbeddedEditorWindow(w) && g_gl.cameraState.valid && QD3D12_WantsDLSSRayReconstruction(); } @@ -7255,6 +7295,7 @@ static bool QD3D12_WantsDLSSFrameGeneration(const QD3D12Window& w) { return !w.isPbuffer && + !QD3D12_IsEmbeddedEditorWindow(w) && g_gl.cameraState.valid && g_qd3d12Sl.deviceBound && g_qd3d12Sl.dlssGSupported && @@ -7271,6 +7312,8 @@ static uint32_t QD3D12_GetDLSSFrameGenerationEligibilityMask(const QD3D12Window& uint32_t mask = 0; if (w.isPbuffer) mask |= 1u << 0; + if (QD3D12_IsEmbeddedEditorWindow(w)) + mask |= 1u << 8; if (!g_gl.cameraState.valid) mask |= 1u << 1; if (!g_qd3d12Sl.deviceBound) @@ -7301,9 +7344,10 @@ static void QD3D12_LogDLSSFrameGenerationEligibility(const QD3D12Window& w, uint } QD3D12_Log( - "DLSS_G inactive: r_frameGen=%u, pbuffer=%d, camera=%d, deviceBound=%d, dlssG=%d, reflex=%d, resources=%d.", + "DLSS_G inactive: r_frameGen=%u, pbuffer=%d, embedded=%d, camera=%d, deviceBound=%d, dlssG=%d, reflex=%d, resources=%d.", g_gl.frameGenerationMultiplier, w.isPbuffer ? 1 : 0, + QD3D12_IsEmbeddedEditorWindow(w) ? 1 : 0, g_gl.cameraState.valid ? 1 : 0, g_qd3d12Sl.deviceBound ? 1 : 0, (g_qd3d12Sl.dlssGSupported && g_qd3d12Sl.dlssGFeatureLoaded) ? 1 : 0, @@ -11466,11 +11510,15 @@ static void QD3D12_ProcessCompletedTextureMipJobs(UINT maxUploads) } } -static void UploadTexture(TextureResource& tex) +static void UploadTexture(TextureResource& tex, ID3D12GraphicsCommandList* commandList) { if (!tex.texture) return; + ID3D12GraphicsCommandList* cl = commandList ? commandList : g_gl.cmdList.Get(); + if (!cl) + return; + if (tex.compressed) { const UINT blockBytes = tex.compressedBlockBytes ? tex.compressedBlockBytes : QD3D12_CompressedTextureBlockBytes(tex.compressedInternalFormat); @@ -11513,7 +11561,7 @@ static void UploadTexture(TextureResource& tex) toCopy.Transition.StateBefore = tex.state; toCopy.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; toCopy.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - g_gl.cmdList->ResourceBarrier(1, &toCopy); + cl->ResourceBarrier(1, &toCopy); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; } @@ -11532,17 +11580,17 @@ static void UploadTexture(TextureResource& tex) dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLoc.SubresourceIndex = 0; - g_gl.cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); + cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); D3D12_RESOURCE_BARRIER toSrv{}; toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toSrv.Transition.pResource = tex.texture.Get(); toSrv.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; - toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; toSrv.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - g_gl.cmdList->ResourceBarrier(1, &toSrv); + cl->ResourceBarrier(1, &toSrv); - tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; tex.gpuValid = true; return; } @@ -11561,7 +11609,7 @@ static void UploadTexture(TextureResource& tex) toCopy.Transition.StateBefore = tex.state; toCopy.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; toCopy.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - g_gl.cmdList->ResourceBarrier(1, &toCopy); + cl->ResourceBarrier(1, &toCopy); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; } @@ -11593,17 +11641,17 @@ static void UploadTexture(TextureResource& tex) dstLoc.pResource = tex.texture.Get(); dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLoc.SubresourceIndex = 0; - g_gl.cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); + cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); D3D12_RESOURCE_BARRIER toSrv{}; toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toSrv.Transition.pResource = tex.texture.Get(); toSrv.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; - toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; toSrv.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - g_gl.cmdList->ResourceBarrier(1, &toSrv); + cl->ResourceBarrier(1, &toSrv); - tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; tex.gpuValid = true; EnsureTextureResource(tex); @@ -12894,12 +12942,20 @@ void APIENTRY glFinish(void) if (!g_gl.device || !g_gl.queue || !g_gl.cmdList) return; + QD3D12Window* owner = g_gl.frameOwner ? g_gl.frameOwner : g_currentWindow; + if (!owner) + return; + if (!g_gl.frameOpen && g_gl.queuedBatches.empty()) { - QD3D12_WaitForGPU(); + if (!QD3D12_IsEmbeddedEditorWindow(*owner)) + QD3D12_WaitForGPU(); return; } + QD3D12Window* savedWindow = g_currentWindow; + g_currentWindow = owner; + QD3D12_EnsureFrameOpen(); QD3D12_FlushQueuedBatches(); @@ -12917,16 +12973,17 @@ void APIENTRY glFinish(void) WaitForSingleObject(g_gl.fenceEvent, INFINITE); } - FrameResources& fr = g_currentWindow->frames[g_currentWindow->frameIndex]; + FrameResources& fr = owner->frames[owner->frameIndex]; fr.fenceValue = signalValue; QD3D12_CHECK(fr.cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(fr.cmdAlloc.Get(), nullptr)); - QD3D12_BindTargetsForCurrentPhase(*g_currentWindow); + QD3D12_BindTargetsForCurrentPhase(*owner); g_gl.frameOpen = true; - g_gl.frameOwner = g_currentWindow; + g_gl.frameOwner = owner; + g_currentWindow = savedWindow; } void APIENTRY glMatrixMode(GLenum mode) @@ -14689,7 +14746,7 @@ void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) } void QD3D12_Resize() { - if (!g_currentWindow) + if (!g_currentWindow || !g_currentWindow->swapChain) return; RECT rc{}; @@ -14718,13 +14775,22 @@ void QD3D12_Resize() QD3D12_ReleaseWindowSizeResources(*g_currentWindow); - QD3D12_CHECK(g_currentWindow->swapChain->ResizeBuffers( + HRESULT resizeHr = g_currentWindow->swapChain->ResizeBuffers( QD3D12_FrameCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING - )); + ); + if (FAILED(resizeHr)) + { + QD3D12_Log("ResizeBuffers failed 0x%08X; recreating swapchain.", (unsigned)resizeHr); + g_currentWindow->swapChain.Reset(); + g_currentWindow->width = width; + g_currentWindow->height = height; + QD3D12_SelectRenderResolution(*g_currentWindow, width, height); + QD3D12_CreateSwapChainForWindow(*g_currentWindow); + } g_currentWindow->width = width; g_currentWindow->height = height; diff --git a/neo/engine/opengl/gl_d3d12wgl.cpp b/neo/engine/opengl/gl_d3d12wgl.cpp index 5bf6be0a..453083bf 100644 --- a/neo/engine/opengl/gl_d3d12wgl.cpp +++ b/neo/engine/opengl/gl_d3d12wgl.cpp @@ -101,6 +101,7 @@ BOOL WINAPI qd3d12_wglMakeCurrent(HDC hdc, QD3D12_HGLRC hglrc) QD3D12FakeContext* ctx = (QD3D12FakeContext*)hglrc; ctx->dc = hdc; + const bool wasInitialized = ctx->initialized; int w = 640, h = 480; @@ -126,16 +127,13 @@ BOOL WINAPI qd3d12_wglMakeCurrent(HDC hdc, QD3D12_HGLRC hglrc) QD3D12_BeginFrame(); } - if (ctx->initialized) { -// glFinish(); - QD3D12_EndFrame(); - } - g_currentDC = hdc; g_currentContext = ctx; QD3D12_SetCurrentWindow(ctx->window); - QD3D12_Resize(); + if (wasInitialized) { + QD3D12_Resize(); + } if (ctx->initialized) { QD3D12_BeginFrame(); @@ -162,11 +160,14 @@ BOOL WINAPI qd3d12_wglDeleteContext(QD3D12_HGLRC hglrc) return FALSE; QD3D12FakeContext* ctx = (QD3D12FakeContext*)hglrc; + const bool embeddedWindow = ctx->hwnd && GetParent(ctx->hwnd) != NULL; if (ctx == g_currentContext) { - if (ctx->initialized) + if (ctx->initialized && !embeddedWindow) QD3D12_ShutdownForQuake(); + else + QD3D12_SetCurrentWindow(NULL); g_currentContext = nullptr; g_currentDC = nullptr; @@ -205,4 +206,4 @@ BOOL WINAPI qd3d12_wglSetDeviceGammaRamp3DFX(HDC hdc, LPVOID ramp) { } __declspec(dllexport) BOOL WINAPI wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD) { return FALSE; } - __declspec(dllexport) BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD) { return FALSE; } \ No newline at end of file + __declspec(dllexport) BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD) { return FALSE; } diff --git a/neo/engine/opengl/opengl.h b/neo/engine/opengl/opengl.h index dc307952..c738f6b6 100644 --- a/neo/engine/opengl/opengl.h +++ b/neo/engine/opengl/opengl.h @@ -1705,6 +1705,10 @@ typedef struct glRaytracingLight_s // spot: x = near clip, y/z unused // rect : scalar range copy float pointRadiusPad; // non-zero disables specular for this light + + uint32_t iesTextureId; // optional GL texture containing a lat-long IES profile + float iesStrength; // 0 disables, 1 uses texture as authored + float iesPad[2]; } glRaytracingLight_t; typedef struct glRaytracingLightingPassDesc_s @@ -1844,6 +1848,7 @@ ID3D12Device* QD3D12_GetDevice(void); ID3D12CommandQueue* QD3D12_GetQueue(void); ID3D12GraphicsCommandList* QD3D12_GetCommandList(void); ID3D12Resource* QD3D12_GetCurrentBackBuffer(void); +ID3D12Resource* QD3D12_GetTextureResource(GLuint texture, DXGI_FORMAT* format, UINT* width, UINT* height, ID3D12GraphicsCommandList* commandList); static float glRaytracingSqrtf(float x) { diff --git a/neo/engine/renderer/Image_files.cpp b/neo/engine/renderer/Image_files.cpp index 9b8aac64..f2f34aee 100644 --- a/neo/engine/renderer/Image_files.cpp +++ b/neo/engine/renderer/Image_files.cpp @@ -35,6 +35,7 @@ you may contact in writing Justin Marshall, justinmarshall20@gmail.com #pragma hdrstop #include "tr_local.h" +#include /* @@ -896,6 +897,235 @@ static void LoadPNG(const char* filename, byte** pic, int* width, int* height, I LoadSTBImage(filename, pic, width, height, timestamp); } +/* +============= +LoadIES + +Converts an LM-63 IES photometric profile into a lat-long RGBA8 lookup: +X = horizontal angle (0..360), Y = vertical angle (0..180). +============= +*/ +static bool IES_ReadFloat(const char*& cursor, const char* end, float& out) { + while (cursor < end) { + const char c = *cursor; + if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') { + break; + } + cursor++; + } + if (cursor >= end) { + return false; + } + + char* parseEnd = NULL; + out = (float)strtod(cursor, &parseEnd); + if (parseEnd == cursor) { + return false; + } + cursor = parseEnd; + return true; +} + +static const char* IES_FindTiltLine(const char* begin, const char* end) { + static const char marker[] = "TILT="; + const int markerLen = sizeof(marker) - 1; + for (const char* p = begin; p + markerLen <= end; p++) { + bool match = true; + for (int i = 0; i < markerLen; i++) { + char a = p[i]; + if (a >= 'a' && a <= 'z') { + a = a - 'a' + 'A'; + } + if (a != marker[i]) { + match = false; + break; + } + } + if (match) { + return p; + } + } + return NULL; +} + +static float IES_SampleArray(const std::vector& values, int count, float angle) { + if (count <= 0 || values.empty()) { + return 0.0f; + } + if (count == 1) { + return values[0]; + } + + if (angle <= values[0]) { + return 0.0f; + } + if (angle >= values[count - 1]) { + return (angle == values[count - 1]) ? (float)(count - 1) : 0.0f; + } + + for (int i = 0; i < count - 1; i++) { + const float a0 = values[i]; + const float a1 = values[i + 1]; + if (angle >= a0 && angle <= a1) { + const float denom = a1 - a0; + const float f = denom > 1e-6f ? (angle - a0) / denom : 0.0f; + return (float)i + f; + } + } + return 0.0f; +} + +static float IES_CandelaAt( + const std::vector& verticalAngles, + const std::vector& horizontalAngles, + const std::vector& candela, + int numVertical, + int numHorizontal, + float vertical, + float horizontal) { + if (numVertical <= 0 || numHorizontal <= 0 || candela.empty()) { + return 0.0f; + } + + horizontal = fmodf(horizontal, 360.0f); + if (horizontal < 0.0f) { + horizontal += 360.0f; + } + + const float vf = IES_SampleArray(verticalAngles, numVertical, vertical); + const int v0 = idMath::ClampInt(0, numVertical - 1, (int)floorf(vf)); + const int v1 = idMath::ClampInt(0, numVertical - 1, v0 + 1); + const float vt = idMath::ClampFloat(0.0f, 1.0f, vf - (float)v0); + + float hf = 0.0f; + if (numHorizontal > 1) { + hf = IES_SampleArray(horizontalAngles, numHorizontal, horizontal); + } + const int h0 = idMath::ClampInt(0, numHorizontal - 1, (int)floorf(hf)); + const int h1 = idMath::ClampInt(0, numHorizontal - 1, h0 + 1); + const float ht = idMath::ClampFloat(0.0f, 1.0f, hf - (float)h0); + + const float c00 = candela[h0 * numVertical + v0]; + const float c10 = candela[h1 * numVertical + v0]; + const float c01 = candela[h0 * numVertical + v1]; + const float c11 = candela[h1 * numVertical + v1]; + const float c0 = c00 + (c10 - c00) * ht; + const float c1 = c01 + (c11 - c01) * ht; + return c0 + (c1 - c0) * vt; +} + +static void LoadIES(const char* filename, byte** pic, int* width, int* height, ID_TIME_T* timestamp) { + byte* fileBuffer = NULL; + const int fileLength = fileSystem->ReadFile(filename, (void**)&fileBuffer, timestamp); + + if (pic) { + *pic = NULL; + } + if (width) { + *width = 0; + } + if (height) { + *height = 0; + } + if (!pic || !fileBuffer || fileLength <= 0) { + if (fileBuffer) { + fileSystem->FreeFile(fileBuffer); + } + return; + } + + const char* begin = (const char*)fileBuffer; + const char* end = begin + fileLength; + const char* numeric = IES_FindTiltLine(begin, end); + if (!numeric || numeric >= end) { + fileSystem->FreeFile(fileBuffer); + return; + } + while (numeric < end && *numeric != '\n' && *numeric != '\r') { + numeric++; + } + + float header[13]; + for (int i = 0; i < 13; i++) { + if (!IES_ReadFloat(numeric, end, header[i])) { + fileSystem->FreeFile(fileBuffer); + return; + } + } + + const int numVertical = idMath::ClampInt(1, 1024, (int)header[3]); + const int numHorizontal = idMath::ClampInt(1, 1024, (int)header[4]); + const float candelaScale = header[2] > 0.0f ? header[2] : 1.0f; + + std::vector verticalAngles(numVertical); + std::vector horizontalAngles(numHorizontal); + std::vector candela(numVertical * numHorizontal); + + for (int i = 0; i < numVertical; i++) { + if (!IES_ReadFloat(numeric, end, verticalAngles[i])) { + fileSystem->FreeFile(fileBuffer); + return; + } + } + for (int i = 0; i < numHorizontal; i++) { + if (!IES_ReadFloat(numeric, end, horizontalAngles[i])) { + fileSystem->FreeFile(fileBuffer); + return; + } + } + + float maxCandela = 0.0f; + for (int h = 0; h < numHorizontal; h++) { + for (int v = 0; v < numVertical; v++) { + float c = 0.0f; + if (!IES_ReadFloat(numeric, end, c)) { + fileSystem->FreeFile(fileBuffer); + return; + } + c *= candelaScale; + if (c < 0.0f) { + c = 0.0f; + } + candela[h * numVertical + v] = c; + if (c > maxCandela) { + maxCandela = c; + } + } + } + + fileSystem->FreeFile(fileBuffer); + + if (maxCandela <= 0.0f) { + return; + } + + const int outWidth = 512; + const int outHeight = 256; + byte* out = (byte*)R_StaticAlloc(outWidth * outHeight * 4); + + for (int y = 0; y < outHeight; y++) { + const float vertical = ((float)y + 0.5f) * (180.0f / (float)outHeight); + for (int x = 0; x < outWidth; x++) { + const float horizontal = ((float)x + 0.5f) * (360.0f / (float)outWidth); + const float c = IES_CandelaAt(verticalAngles, horizontalAngles, candela, numVertical, numHorizontal, vertical, horizontal); + const byte v = (byte)idMath::ClampInt(0, 255, (int)(255.0f * idMath::ClampFloat(0.0f, 1.0f, c / maxCandela) + 0.5f)); + byte* dst = out + ((y * outWidth + x) * 4); + dst[0] = v; + dst[1] = v; + dst[2] = v; + dst[3] = 255; + } + } + + *pic = out; + if (width) { + *width = outWidth; + } + if (height) { + *height = outHeight; + } +} + //=================================================================== /* @@ -979,6 +1209,9 @@ void R_LoadImage(const char* cname, byte** pic, int* width, int* height, ID_TIME else if (ext == "png") { LoadPNG(name.c_str(), pic, width, height, timestamp); } + else if (ext == "ies") { + LoadIES(name.c_str(), pic, width, height, timestamp); + } if ((width && *width < 1) || (height && *height < 1)) { if (pic && *pic) { @@ -1149,4 +1382,4 @@ bool R_LoadCubeImages(const char* imgName, cubeFiles_t extensions, byte* pics[6] } return true; -} \ No newline at end of file +} diff --git a/neo/engine/renderer/Material.cpp b/neo/engine/renderer/Material.cpp index 5ead2ab8..ef292d80 100644 --- a/neo/engine/renderer/Material.cpp +++ b/neo/engine/renderer/Material.cpp @@ -131,6 +131,7 @@ void idMaterial::CommonInit() { stages = NULL; editorImage = NULL; lightFalloffImage = NULL; + lightIESImage = NULL; shouldCreateBackSides = false; entityGui = 0; fogLight = false; @@ -2592,6 +2593,9 @@ void idMaterial::ResolveUse(void) { if (lightFalloffImage) { lightFalloffImage->useCount += useCount; } + if (lightIESImage) { + lightIESImage->useCount += useCount; + } if (portalImage) { portalImage->useCount += useCount; } @@ -3030,6 +3034,17 @@ void idMaterial::ParseMaterial(idLexer& src) { lightFalloffImage = globalImages->ImageFromFile(copy, TF_DEFAULT, false, TR_CLAMP /* TR_CLAMP_TO_ZERO */, TD_DEFAULT); continue; } + else if (!token.Icmp("iesTexture") || !token.Icmp("lightIESTexture")) { + str = R_ParsePastImageProgram(src); + idStr copy = str; + idStr ext; + copy.ExtractFileExtension(ext); + if (ext.IsEmpty()) { + copy.DefaultFileExtension(".ies"); + } + lightIESImage = globalImages->ImageFromFile(copy, TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY); + continue; + } // guisurf | guisurf entity // an entity guisurf must have an idUserInterface // specified in the renderEntity @@ -3256,6 +3271,15 @@ void idMaterial::ParseMaterial(idLexer& src) { } } + if (lightIESImage && numStages == 0 && !fogLight && !blendLight && !ambientLight) { + char buffer[1024]; + idLexer newSrc; + + idStr::snPrintf(buffer, sizeof(buffer), "blend diffusemap\nmap _white\n}\n"); + newSrc.LoadMemory(buffer, strlen(buffer), "iesLight"); + ParseStage(newSrc, trpDefault); + } + // add _flat or _white stages if needed AddImplicitStages(); diff --git a/neo/engine/renderer/Material.h b/neo/engine/renderer/Material.h index d2873d08..226627c5 100644 --- a/neo/engine/renderer/Material.h +++ b/neo/engine/renderer/Material.h @@ -764,6 +764,7 @@ public: // NULL unless an image is explicitly specified in the shader with "lightFalloffShader " idImage* LightFalloffImage() const { return lightFalloffImage; } + idImage* LightIESImage() const { return lightIESImage; } //------------------------------------------------------------------ @@ -927,6 +928,7 @@ private: idStr renderBump; // renderbump command options, without the "renderbump" at the start idImage* lightFalloffImage; + idImage* lightIESImage; int entityGui; // draw a gui with the idUserInterface from the renderEntity_t // non zero will draw gui, gui2, or gui3 from renderEnitty_t diff --git a/neo/engine/renderer/RenderEntity.cpp b/neo/engine/renderer/RenderEntity.cpp index ad2ad201..dcd30524 100644 --- a/neo/engine/renderer/RenderEntity.cpp +++ b/neo/engine/renderer/RenderEntity.cpp @@ -99,6 +99,7 @@ idRenderLightLocal::idRenderLightLocal() { archived = false; lightShader = NULL; falloffImage = NULL; + iesImage = NULL; globalLightOrigin = vec3_zero; frustumTris = NULL; numShadowFrustums = 0; diff --git a/neo/engine/renderer/RenderSystem_init.cpp b/neo/engine/renderer/RenderSystem_init.cpp index b03aa7d4..9dafb4e5 100644 --- a/neo/engine/renderer/RenderSystem_init.cpp +++ b/neo/engine/renderer/RenderSystem_init.cpp @@ -81,7 +81,7 @@ idCVar r_znear( "r_znear", "3", CVAR_RENDERER | CVAR_FLOAT, "near Z clip plane d idCVar r_ignoreGLErrors( "r_ignoreGLErrors", "1", CVAR_RENDERER | CVAR_BOOL, "ignore GL errors" ); idCVar r_finish( "r_finish", "0", CVAR_RENDERER | CVAR_BOOL, "force a call to glFinish() every frame" ); idCVar r_swapInterval( "r_swapInterval", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "changes wglSwapIntarval" ); -idCVar r_frameGen( "r_frameGen", "2", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "DLSS frame generation multiplier: 0/1 = off, 2 = 2x, 3 = 3x, 4 = 4x", 0, 4, idCmdSystem::ArgCompletion_Integer<0,4> ); +idCVar r_frameGen( "r_frameGen", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "DLSS frame generation multiplier: 0/1 = off, 2 = 2x, 3 = 3x, 4 = 4x", 0, 4, idCmdSystem::ArgCompletion_Integer<0,4> ); idCVar r_gamma( "r_gamma", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 3.0f ); idCVar r_brightness( "r_brightnesspt", "1.5", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 2.0f ); diff --git a/neo/engine/renderer/draw_dx.cpp b/neo/engine/renderer/draw_dx.cpp index f0ec9266..a42a26f1 100644 --- a/neo/engine/renderer/draw_dx.cpp +++ b/neo/engine/renderer/draw_dx.cpp @@ -201,6 +201,19 @@ void RB_DXDrawInteractions(void) light.samples = srcLight.noShadows ? 0u : 1u; light.pointRadiusPad = srcLight.noSpecular ? 1.0f : 0.0f; + + const idVec3 axisU = RB_DXRNormalizeVec3Safe(RB_DXRTransformLightVector(srcLight.axis, RB_DXRMakeVec3(1.0f, 0.0f, 0.0f)), RB_DXRMakeVec3(1.0f, 0.0f, 0.0f)); + const idVec3 axisV = RB_DXRNormalizeVec3Safe(RB_DXRTransformLightVector(srcLight.axis, RB_DXRMakeVec3(0.0f, 1.0f, 0.0f)), RB_DXRMakeVec3(0.0f, 1.0f, 0.0f)); + const idVec3 axisW = RB_DXRNormalizeVec3Safe(RB_DXRTransformLightVector(srcLight.axis, RB_DXRMakeVec3(0.0f, 0.0f, 1.0f)), RB_DXRMakeVec3(0.0f, 0.0f, 1.0f)); + light.axisU.x = axisU.x; + light.axisU.y = axisU.y; + light.axisU.z = axisU.z; + light.axisV.x = axisV.x; + light.axisV.y = axisV.y; + light.axisV.z = axisV.z; + light.normal.x = axisW.x; + light.normal.y = axisW.y; + light.normal.z = axisW.z; } else if (!srcLight.parallel) { @@ -219,6 +232,13 @@ void RB_DXDrawInteractions(void) } if (supported) { + if (vLight->iesImage) { + vLight->iesImage->Bind(); + if (vLight->iesImage->texnum != idImage::TEXTURE_NOT_LOADED) { + light.iesTextureId = vLight->iesImage->texnum; + light.iesStrength = 1.0f; + } + } glRaytracingLightingAddLight(&light); } } diff --git a/neo/engine/renderer/tr_light.cpp b/neo/engine/renderer/tr_light.cpp index 9683271f..e8dc2e47 100644 --- a/neo/engine/renderer/tr_light.cpp +++ b/neo/engine/renderer/tr_light.cpp @@ -498,6 +498,7 @@ viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *light ) { vLight->fogPlane = light->frustum[5]; vLight->frustumTris = light->frustumTris; vLight->falloffImage = light->falloffImage; + vLight->iesImage = light->iesImage; vLight->lightShader = light->lightShader; vLight->shaderRegisters = NULL; // allocated and evaluated in R_AddLightSurfaces diff --git a/neo/engine/renderer/tr_lightrun.cpp b/neo/engine/renderer/tr_lightrun.cpp index 8bcf7b70..9c4803ba 100644 --- a/neo/engine/renderer/tr_lightrun.cpp +++ b/neo/engine/renderer/tr_lightrun.cpp @@ -365,6 +365,7 @@ void R_DeriveLightData( idRenderLightLocal *light ) { // get the falloff image light->falloffImage = light->lightShader->LightFalloffImage(); + light->iesImage = light->lightShader->LightIESImage(); if ( !light->falloffImage ) { // use the falloff from the default shader of the correct type const idMaterial *defaultShader; diff --git a/neo/engine/renderer/tr_local.h b/neo/engine/renderer/tr_local.h index a9b72211..3a673247 100644 --- a/neo/engine/renderer/tr_local.h +++ b/neo/engine/renderer/tr_local.h @@ -209,6 +209,7 @@ public: const idMaterial * lightShader; // guaranteed to be valid, even if parms.shader isn't idImage * falloffImage; + idImage * iesImage; idVec3 globalLightOrigin; // accounting for lightCenter and parallel @@ -322,6 +323,7 @@ typedef struct viewLight_s { const idMaterial * lightShader; // light shader used by backend const float * shaderRegisters; // shader registers used by backend idImage * falloffImage; // falloff image used by backend + idImage * iesImage; // optional IES photometric profile const struct drawSurf_s *globalShadows; // shadow everything const struct drawSurf_s *localInteractions; // don't get local shadows