From baeae0018509955d87f65fe1da3483903e0aca61 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Tue, 5 May 2026 21:14:31 -0700 Subject: [PATCH] Added emissive texture support --- neo/opengl/gl_d3d12raylight.cpp | 133 ++++++++- neo/opengl/gl_d3d12shim.cpp | 473 +++++++++++++++++++++++++++++--- neo/opengl/opengl.h | 15 +- neo/renderer/Material.cpp | 29 +- neo/renderer/Material.h | 4 +- neo/renderer/draw_common.cpp | 293 ++------------------ 6 files changed, 614 insertions(+), 333 deletions(-) diff --git a/neo/opengl/gl_d3d12raylight.cpp b/neo/opengl/gl_d3d12raylight.cpp index 6fa77a98..a3f8e86b 100644 --- a/neo/opengl/gl_d3d12raylight.cpp +++ b/neo/opengl/gl_d3d12raylight.cpp @@ -2287,6 +2287,8 @@ struct glRaytracingLightingState_t DXGI_FORMAT denoiseFormat; uint32_t frameCounter; bool externalDenoiser; + ID3D12Resource* emissiveTexture; + DXGI_FORMAT emissiveFormat; bool uploadToCurrentFrameResource; bool initialized; @@ -2311,6 +2313,8 @@ struct glRaytracingLightingState_t currentHistoryIndex = 0; frameCounter = 0; externalDenoiser = false; + emissiveTexture = nullptr; + emissiveFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; uploadToCurrentFrameResource = false; initialized = false; } @@ -2333,14 +2337,15 @@ enum glRaytracingLightingDescriptorIndex_t GLR_DESC_DENOISE_B_SRV = 8, GLR_DESC_HISTORY_SRV = 9, GLR_DESC_TEMPORAL_SRV = 10, - GLR_DESC_PATHTRACE_UAV = 11, - GLR_DESC_DENOISE_A_UAV = 12, - GLR_DESC_DENOISE_B_UAV = 13, - GLR_DESC_OUTPUT_UAV = 14, - GLR_DESC_TEMPORAL_UAV = 15, - GLR_DESC_HISTORY_UAV = 16, - GLR_DESC_COUNT = 17, - GLR_DESC_SRV_COUNT = 11, + GLR_DESC_EMISSIVE_SRV = 11, + GLR_DESC_PATHTRACE_UAV = 12, + GLR_DESC_DENOISE_A_UAV = 13, + GLR_DESC_DENOISE_B_UAV = 14, + GLR_DESC_OUTPUT_UAV = 15, + GLR_DESC_TEMPORAL_UAV = 16, + GLR_DESC_HISTORY_UAV = 17, + GLR_DESC_COUNT = 18, + GLR_DESC_SRV_COUNT = 12, GLR_DESC_UAV_COUNT = 6 }; @@ -2434,6 +2439,7 @@ Texture2D gDepthTex : register(t2); Texture2D gNormalTex : register(t3); Texture2D gPositionTex : register(t4); RaytracingAccelerationStructure gSceneBVH : register(t5); +Texture2D gEmissiveTex : register(t11); RWTexture2D gOutputTex : register(u0); static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0; @@ -3373,6 +3379,61 @@ float TraceVisibilityBiased(float3 worldPos, float3 N, float3 dir, float maxT) 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); + return CompressEmissiveRadiance(gEmissiveTex.Load(int3(p, 0)).rgb, 7.50); +} + +float3 EstimateEmissiveBloomAtPixel(uint2 pixel) +{ + int2 p = int2(pixel); + + // Strong threshold-free bloom. This stays direct/bloom-only: no extra + // TraceRay calls, and no emissive lighting contribution. The goal is to make + // visible glow maps read as hot emissive surfaces in the DXR output. + float3 bloom = 0.0; + bloom += LoadEmissiveRadianceClamped(p) * 0.180; + + bloom += LoadEmissiveRadianceClamped(p + int2( 1, 0)) * 0.220; + bloom += LoadEmissiveRadianceClamped(p + int2(-1, 0)) * 0.220; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, 1)) * 0.220; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, -1)) * 0.220; + + bloom += LoadEmissiveRadianceClamped(p + int2( 2, 2)) * 0.135; + bloom += LoadEmissiveRadianceClamped(p + int2(-2, 2)) * 0.135; + bloom += LoadEmissiveRadianceClamped(p + int2( 2, -2)) * 0.135; + bloom += LoadEmissiveRadianceClamped(p + int2(-2, -2)) * 0.135; + + bloom += LoadEmissiveRadianceClamped(p + int2( 4, 0)) * 0.090; + bloom += LoadEmissiveRadianceClamped(p + int2(-4, 0)) * 0.090; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, 4)) * 0.090; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, -4)) * 0.090; + + bloom += LoadEmissiveRadianceClamped(p + int2( 8, 0)) * 0.060; + bloom += LoadEmissiveRadianceClamped(p + int2(-8, 0)) * 0.060; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, 8)) * 0.060; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, -8)) * 0.060; + + bloom += LoadEmissiveRadianceClamped(p + int2( 14, 0)) * 0.035; + bloom += LoadEmissiveRadianceClamped(p + int2(-14, 0)) * 0.035; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, 14)) * 0.035; + bloom += LoadEmissiveRadianceClamped(p + int2( 0, -14)) * 0.035; + + return bloom * 0.78; +} + float3 SafeNormalizeOr(float3 v, float3 fallback) { float lenSq = dot(v, v); @@ -3717,7 +3778,8 @@ float EstimateVolumeDensityFromLight(Light Lgt) 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) @@ -3927,8 +3989,8 @@ float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitV = SafeNormalizeOr(hitV, -hitN); hitAlbedo = saturate(hitAlbedo); - // Treat unlit G-buffer surfaces as simple emissive bounce cards. Clamp so a - // white UI/light-card cannot become an uncontrolled firefly in the GI path. + // 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); @@ -3939,6 +4001,10 @@ float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 // 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. @@ -4299,6 +4365,9 @@ float3 PathTraceDeterministicLighting( lightingAccum += skyColor * (0.70 * skyVis); lightingAccum += ambientSkyVis * (skyColorRGB * 0.15); + // 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; @@ -4344,11 +4413,15 @@ void RayGen() 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] = albedoSample; + gOutputTex[pixel] = float4(albedoSample.rgb + emissiveSurface + emissiveBloom, albedoSample.a); return; } @@ -4365,7 +4438,7 @@ void RayGen() if (isUnlit) { - gOutputTex[pixel] = float4(baseAlbedo, albedoSample.a); + gOutputTex[pixel] = float4(baseAlbedo + emissiveSurface + emissiveBloom, albedoSample.a); return; } @@ -4454,6 +4527,12 @@ void RayGen() uint volumeRng = InitRng(pixel, 0u, 0x51u); finalColor += EstimatePathTracedVolumetricScattering(pixel, worldPos, volumeRng); + // Glow-map emission is direct radiance from the primary surface. It is added + // after lighting so it remains visible in darkness and under ray-traced shadows. + // emissiveBloom is the threshold-free halo, so emissive always blooms even if + // the eventual swap-chain/backbuffer is LDR. + finalColor += emissiveSurface + emissiveBloom; + gOutputTex[pixel] = float4(max(finalColor, 0.0), albedoSample.a); } )"; @@ -4623,7 +4702,10 @@ void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID) if (depthSample <= 0.0 || depthSample >= 1.0) { - StoreDenoiseOutput(pixel, albedoSample); + // 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; } @@ -4867,7 +4949,9 @@ static ComPtr glRaytracingLightingCompileLibrary(const char* src) L"-Zi", L"-Qembed_debug", #endif - L"-O3", + // 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" }; @@ -5662,6 +5746,14 @@ static void glRaytracingLightingCreatePerPassDescriptors( 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_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {}; rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; rayOutputUav.Format = GL_RAYTRACING_DENOISE_FORMAT; @@ -6299,6 +6391,17 @@ void glRaytracingLightingUseExternalDenoiser(int enabled) glRaytracingLightingSetExternalDenoiser(enabled); } + +void glRaytracingLightingSetEmissiveInput(ID3D12Resource* texture, DXGI_FORMAT format) +{ + std::lock_guard lock(g_glRaytracingMutex); + + g_glRaytracingLighting.emissiveTexture = texture; + g_glRaytracingLighting.emissiveFormat = (format == DXGI_FORMAT_UNKNOWN) + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : format; +} + bool glRaytracingLightingExecuteForScene(const glRaytracingLightingPassDesc_t* pass, glRaytracingSceneHandle_t worldHandle) { std::lock_guard lock(g_glRaytracingMutex); diff --git a/neo/opengl/gl_d3d12shim.cpp b/neo/opengl/gl_d3d12shim.cpp index 43a8e108..55fc5a07 100644 --- a/neo/opengl/gl_d3d12shim.cpp +++ b/neo/opengl/gl_d3d12shim.cpp @@ -129,6 +129,9 @@ using Microsoft::WRL::ComPtr; #ifndef GL_NORMAL_MAP_BINDING_QD3D12 #define GL_NORMAL_MAP_BINDING_QD3D12 0x6002 #endif +#ifndef GL_GLOW_MAP_BINDING_QD3D12 +#define GL_GLOW_MAP_BINDING_QD3D12 0x6005 +#endif // Optional material/ray-visibility tags for the DXR path. These are private // shim enums; they deliberately live next to the normal-map compatibility enums. @@ -186,6 +189,11 @@ void APIENTRY glBinormal3f(GLfloat x, GLfloat y, GLfloat z); void APIENTRY glBinormal3fv(const GLfloat* v); void APIENTRY glGlassMaterialQD3D12(GLboolean enable); void APIENTRY glMaterialGlassQD3D12(GLboolean enable); +void APIENTRY glTagTextureGlowMap(GLuint texture, GLboolean isGlowMap); +void APIENTRY glTextureGlowMap(GLuint texture, GLboolean isGlowMap); +void APIENTRY glBindGlowMapTexture(GLuint texture); +void APIENTRY glGlowMapTexture(GLuint texture); +void APIENTRY glGlowMapStrengthf(GLfloat strength); void APIENTRY glRaytracingMaterialFlagsQD3D12(GLuint flags); void APIENTRY glRaytracingMaterialFlagQD3D12(GLuint flag, GLboolean enable); @@ -195,6 +203,7 @@ void APIENTRY glRaytracingMaterialFlagQD3D12(GLuint flag, GLboolean enable); void glRaytracingLightingSetExternalDenoiser(int enable); void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_t maxBounces, int enableDenoiser, float denoiseStrength); void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); +void glRaytracingLightingSetEmissiveInput(ID3D12Resource* texture, DXGI_FORMAT format); void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint32_t materialFlags); void glRaytracingSetMeshGlass(glRaytracingMeshHandle_t meshHandle, int isGlass); uint32_t glRaytracingGetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle); @@ -312,15 +321,18 @@ enum QD3D12RTVSlotGroup QD3D12_RTV_NORMAL_RENDER = 1, QD3D12_RTV_POSITION_RENDER = 2, QD3D12_RTV_VELOCITY_RENDER = 3, - QD3D12_RTV_SCENE_RESOLVED = 4, - QD3D12_RTV_NORMAL_RESOLVED = 5, - QD3D12_RTV_POSITION_RESOLVED = 6, - QD3D12_RTV_VELOCITY_RESOLVED = 7, - QD3D12_RTV_BACKBUFFER = 8, - QD3D12_RTV_GROUP_COUNT = 9 + QD3D12_RTV_EMISSIVE_RENDER = 4, + QD3D12_RTV_SCENE_RESOLVED = 5, + QD3D12_RTV_NORMAL_RESOLVED = 6, + QD3D12_RTV_POSITION_RESOLVED = 7, + QD3D12_RTV_VELOCITY_RESOLVED = 8, + QD3D12_RTV_EMISSIVE_RESOLVED = 9, + QD3D12_RTV_BACKBUFFER = 10, + QD3D12_RTV_GROUP_COUNT = 11 }; static const DXGI_FORMAT QD3D12_SceneColorFormat = DXGI_FORMAT_R8G8B8A8_UNORM; +static const DXGI_FORMAT QD3D12_EmissiveFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; static const DXGI_FORMAT QD3D12_StreamlineOutputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; static const DXGI_FORMAT QD3D12_VelocityFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; static const DXGI_FORMAT QD3D12_DepthFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT; @@ -444,11 +456,12 @@ struct TextureResource bool gpuValid = false; D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST; - // Optional material role tag. Tagged textures are not treated as diffuse - // inputs by default; they are selected as tangent-space normal maps for the - // fixed-function G-buffer path when bound through glNormalMapTexture() or any - // active texture unit. + // Optional material role tags. Tagged textures are not treated as diffuse + // inputs by default; they are selected as tangent-space normal/glow maps for + // the fixed-function G-buffer path when explicitly bound or auto-discovered + // from any active texture unit. bool isNormalMap = false; + bool isGlowMap = false; }; static void QD3D12_ShutdownMipWorkers(); @@ -523,7 +536,7 @@ struct GLBufferObject const char* vendor = "Justin Marshall"; const char* renderer = "Quake D3D12 Wrapper"; const char* version = "1.1-quake-d3d12"; -const char* extensions = "GL_SGIS_multitexture GL_ARB_multitexture GL_EXT_texture_env_add GL_ARB_texture_env_combine GL_ARB_texture_compression GL_EXT_texture_compression_s3tc GL_ARB_vertex_program GL_ARB_fragment_program GL_EXT_texture_cube_map GL_EXT_depth_bounds_test GL_EXT_stencil_two_side GL_ATI_separate_stencil GL_QD3D12_normal_map GL_QD3D12_glass_material GL_QD3D12_volumetric_light"; +const char* extensions = "GL_SGIS_multitexture GL_ARB_multitexture GL_EXT_texture_env_add GL_ARB_texture_env_combine GL_ARB_texture_compression GL_EXT_texture_compression_s3tc GL_ARB_vertex_program GL_ARB_fragment_program GL_EXT_texture_cube_map GL_EXT_depth_bounds_test GL_EXT_stencil_two_side GL_ATI_separate_stencil GL_QD3D12_normal_map GL_QD3D12_glow_map GL_QD3D12_glass_material GL_QD3D12_volumetric_light"; enum TexEnvModeShader { @@ -568,6 +581,9 @@ struct BatchKey float useNormalMap = 0.0f; float normalMapStrength = 1.0f; float normalMapYSign = 1.0f; + UINT glowMapSrvIndex = 0; + float useGlowMap = 0.0f; + float glowMapStrength = 1.0f; bool useARBPrograms = false; GLuint arbVertexProgram = 0; @@ -676,6 +692,9 @@ static bool BatchKeyEquals(const BatchKey& a, const BatchKey& b) a.useNormalMap == b.useNormalMap && a.normalMapStrength == b.normalMapStrength && a.normalMapYSign == b.normalMapYSign && + a.glowMapSrvIndex == b.glowMapSrvIndex && + a.useGlowMap == b.useGlowMap && + a.glowMapStrength == b.glowMapStrength && a.alphaRef == b.alphaRef && a.alphaFunc == b.alphaFunc && a.useTex0 == b.useTex0 && @@ -779,6 +798,9 @@ struct QD3D12Window std::array, QD3D12_FrameCount> velocityBuffers; D3D12_RESOURCE_STATES velocityBufferState[QD3D12_FrameCount] = {}; + std::array, QD3D12_FrameCount> emissiveBuffers; + D3D12_RESOURCE_STATES emissiveBufferState[QD3D12_FrameCount] = {}; + std::array, QD3D12_FrameCount> sceneColorMsaaBuffers; D3D12_RESOURCE_STATES sceneColorMsaaState[QD3D12_FrameCount] = {}; @@ -791,6 +813,9 @@ struct QD3D12Window std::array, QD3D12_FrameCount> velocityMsaaBuffers; D3D12_RESOURCE_STATES velocityMsaaState[QD3D12_FrameCount] = {}; + std::array, QD3D12_FrameCount> emissiveMsaaBuffers; + D3D12_RESOURCE_STATES emissiveMsaaState[QD3D12_FrameCount] = {}; + std::array, QD3D12_FrameCount> backBuffers; D3D12_RESOURCE_STATES backBufferState[QD3D12_FrameCount] = {}; @@ -824,6 +849,10 @@ struct QD3D12Window D3D12_CPU_DESCRIPTOR_HANDLE velocitySrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE velocitySrvGpu[QD3D12_FrameCount]{}; + UINT emissiveSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE emissiveSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE emissiveSrvGpu[QD3D12_FrameCount]{}; + UINT slOutputSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE slOutputSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE slOutputSrvGpu[QD3D12_FrameCount]{}; @@ -848,6 +877,10 @@ struct QD3D12Window D3D12_CPU_DESCRIPTOR_HANDLE velocityMsaaSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE velocityMsaaSrvGpu[QD3D12_FrameCount]{}; + UINT emissiveMsaaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE emissiveMsaaSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE emissiveMsaaSrvGpu[QD3D12_FrameCount]{}; + D3D12_VIEWPORT viewport{}; D3D12_RECT scissor{}; @@ -1119,6 +1152,8 @@ struct GLState GLuint currentNormalMapTexture = 0; float currentNormalMapStrength = 1.0f; float currentNormalMapYSign = 1.0f; + GLuint currentGlowMapTexture = 0; + float currentGlowMapStrength = 1.0f; ImmediateVertexBuffer immediateVerts; GLenum matrixMode = GL_MODELVIEW; @@ -1703,6 +1738,12 @@ static D3D12_CPU_DESCRIPTOR_HANDLE CurrentVelocityRTV() return QD3D12_RtvAt(w, QD3D12_RTV_VELOCITY_RENDER, w.frameIndex); } +static D3D12_CPU_DESCRIPTOR_HANDLE CurrentEmissiveRTV() +{ + QD3D12Window& w = *g_currentWindow; + return QD3D12_RtvAt(w, QD3D12_RTV_EMISSIVE_RENDER, w.frameIndex); +} + static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedSceneColorRTV() { QD3D12Window& w = *g_currentWindow; @@ -1727,6 +1768,12 @@ static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedVelocityRTV() return QD3D12_RtvAt(w, QD3D12_RTV_VELOCITY_RESOLVED, w.frameIndex); } +static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedEmissiveRTV() +{ + QD3D12Window& w = *g_currentWindow; + return QD3D12_RtvAt(w, QD3D12_RTV_EMISSIVE_RESOLVED, w.frameIndex); +} + static D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferRTV() { QD3D12Window& w = *g_currentWindow; @@ -1815,6 +1862,12 @@ static bool QD3D12_IsTextureTaggedNormalMap(GLuint id) return tex && tex->isNormalMap; } +static bool QD3D12_IsTextureTaggedGlowMap(GLuint id) +{ + TextureResource* tex = QD3D12_FindTextureResource(id); + return tex && tex->isGlowMap; +} + static TextureResource* QD3D12_SelectNormalMapTexture(TextureResource* const* allTextures) { // Explicit material binding wins. This lets callers bind a normal map even @@ -1847,6 +1900,45 @@ static TextureResource* QD3D12_SelectNormalMapTexture(TextureResource* const* al return nullptr; } +static TextureResource* QD3D12_SelectGlowMapTexture(TextureResource* const* allTextures) +{ + // Explicit material binding wins. Tagging enables automatic discovery from + // arbitrary texture units without consuming the diffuse/lightmap slots. + if (g_gl.currentGlowMapTexture != 0) + { + TextureResource* explicitGlow = QD3D12_FindTextureResource(g_gl.currentGlowMapTexture); + if (explicitGlow) + { + // Common integration mistake: passing shader->GetBumpImage()->texnum + // to glBindGlowMapTexture(). Never treat a texture explicitly tagged + // as a normal map as emissive unless it was also explicitly tagged glow. + if (explicitGlow->isNormalMap && !explicitGlow->isGlowMap) + return nullptr; + + return explicitGlow; + } + } + + if (allTextures) + { + for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) + { + TextureResource* tex = allTextures[i]; + if (tex && tex != &g_gl.whiteTexture && tex->isGlowMap) + return tex; + } + } + + for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) + { + TextureResource* tex = QD3D12_FindTextureResource(g_gl.boundTexture[i]); + if (tex && tex->isGlowMap) + return tex; + } + + return nullptr; +} + static const uint8_t* QD3D12_ResolveArrayPointer(const void* ptr) { if (g_gl.boundArrayBuffer != 0) @@ -2005,14 +2097,18 @@ cbuffer DrawCB : register(b0) #define gUseNormalMap gMotionPad.x #define gNormalMapStrength gMotionPad.y #define gNormalMapYSign gMotionPad.z -#define gParallaxScale gMotionPad.w +#define gParallaxScale 0.0 +#define gUseGlowMap gMotionPad.w +#define gGlowMapStrength gMotionPad.w Texture2D gTex0 : register(t0); Texture2D gTex1 : register(t1); Texture2D gNormalMap : register(t2); +Texture2D gGlowMap : register(t3); SamplerState gSamp0 : register(s0); SamplerState gSamp1 : register(s1); SamplerState gSamp2 : register(s2); +SamplerState gSamp3 : register(s3); struct VSIn { @@ -2048,6 +2144,7 @@ struct PSOut float4 normal : SV_Target1; float4 position : SV_Target2; float4 velocity : SV_Target3; + float4 emissive : SV_Target4; }; float4 QD3D12_GetTexEnvSource(float source, float4 texel, float4 primary, float4 previous, float4 constantColor) @@ -2306,21 +2403,61 @@ float3 BuildGBufferNormal(VSOut i) n); } +float2 QD3D12_BuildMaterialUV0(VSOut i) +{ + float2 uv0 = i.uv0; + float n = TinyNoise(int2(i.pos.xy)) * 0.0005; + uv0 += float2(n, -n); + + if (gUseNormalMap > 0.5) + uv0 = QD3D12_ComputeParallaxUV(i, uv0); + + return uv0; +} + +float4 QD3D12_SampleGlow(VSOut i) +{ + if (gUseGlowMap <= 0.0) + return float4(0.0, 0.0, 0.0, 0.0); + + float4 glow = gGlowMap.Sample(gSamp3, QD3D12_BuildMaterialUV0(i)); + + // RGB-only glow maps are uploaded with an opaque alpha channel. Treat an + // all-white/forced alpha as "no alpha mask" and derive coverage from RGB; + // otherwise black areas of an RGB glow texture make the base material vanish. + float rgbMask = saturate(max(max(glow.r, glow.g), glow.b)); + bool alphaLooksForcedOpaque = (glow.a >= 0.999); + float glowMask = alphaLooksForcedOpaque ? rgbMask : saturate(max(glow.a, rgbMask)); + float emissionMask = alphaLooksForcedOpaque ? 1.0 : glowMask; + + // Store bright HDR radiance in the FP16 emissive G-buffer. This is still + // direct/bloom-only; emissive lighting has intentionally stayed disabled. + // The higher baseline lets normal idTech glow maps read as obvious emission + // without every material having to call glGlowMapStrengthf(3+). + const float kDefaultGlowBloomRadiance = 3.25; + float strength = max(gGlowMapStrength, 0.0); + + // Low-value glow maps looked too weak after the no-light hotfix. Give the + // emissive RGB a mild artist-friendly lift while preserving black texels. + float3 glowRgb = max(glow.rgb, 0.0); + float3 glowLift = sqrt(saturate(glowRgb)); + float3 emissionColor = max(glowRgb, glowLift * 0.55); + float3 emission = emissionColor * emissionMask * strength * kDefaultGlowBloomRadiance; + + return float4(emission, glowMask); +} + float4 BuildTexturedColor(VSOut i) { float4 primary = i.col; float4 outColor = primary; - float2 uv0 = i.uv0; + float2 uv0 = QD3D12_BuildMaterialUV0(i); float2 uv1 = i.uv1; float n = TinyNoise(int2(i.pos.xy)) * 0.0005; - uv0 += float2(n, -n); uv1 += float2(-n, n); - if (gUseNormalMap > 0.5) - uv0 = QD3D12_ComputeParallaxUV(i, uv0); - if (gUseTex0 > 0.5) { float4 tex0 = gTex0.Sample(gSamp0, uv0); @@ -2335,12 +2472,23 @@ float4 BuildTexturedColor(VSOut i) gTexComb1RGB, gTexComb1Alpha, gTexComb1Operand, gTexEnvColor1); } + if (gUseGlowMap > 0.0) + { + float glowMask = saturate(QD3D12_SampleGlow(i).a); + outColor.rgb *= (1.0 - glowMask); + } + // outColor.xyz = ApplySoftwareRendererLook(outColor.xyz); outColor = ApplyFog(outColor, i.fogCoord); return outColor; } +float4 BuildGlowEmission(VSOut i) +{ + return QD3D12_SampleGlow(i); +} + float2 ClipToUv(float4 clipPos) { float2 ndc = clipPos.xy / max(abs(clipPos.w), 1e-6); @@ -2397,6 +2545,7 @@ PSOut PSMain(VSOut i) o.normal = float4(BuildGBufferNormal(i), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); + o.emissive = BuildGlowEmission(i); return o; } @@ -2408,6 +2557,7 @@ PSOut PSMainAlphaTest(VSOut i) o.normal = float4(BuildGBufferNormal(i), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); + o.emissive = BuildGlowEmission(i); return o; } @@ -2418,18 +2568,22 @@ PSOut PSMainUntextured(VSOut i) o.normal = float4(BuildGBufferNormal(i), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); + o.emissive = float4(0.0, 0.0, 0.0, 0.0); return o; } float4 PSMainColorOnly(VSOut i) : SV_Target0 { - return BuildTexturedColor(i); + float4 c = BuildTexturedColor(i); + c.rgb += BuildGlowEmission(i).rgb; + return c; } float4 PSMainAlphaTestColorOnly(VSOut i) : SV_Target0 { float4 c = BuildTexturedColor(i); QD3D12_AlphaTest(c.a); + c.rgb += BuildGlowEmission(i).rgb; return c; } @@ -2445,6 +2599,7 @@ Texture2DMS gDepthMS : register(t1); Texture2DMS gNormalMS : register(t2); Texture2DMS gPositionMS : register(t3); Texture2DMS gVelocityMS : register(t4); +Texture2DMS gEmissiveMS : register(t5); SamplerState gSamp0 : register(s0); struct VSOut @@ -2471,9 +2626,61 @@ float4 PSCopy(VSOut i) : SV_Target0 return gTex0.Sample(gSamp0, i.uv); } +float3 QD3D12_LoadPostRadiance(float2 uv) +{ + // Clamp bloom taps so a single bad HDR edge sample cannot flood the LDR + // scene-color composite. The direct center emissive value is still added by + // PSAdd below, so this only limits the halo. + return min(max(gTex0.Sample(gSamp0, saturate(uv)).rgb, 0.0), 12.0); +} + +float3 QD3D12_EmissiveBloom(float2 uv) +{ + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); + + // Strong threshold-free bloom kernel. Direct emissive is still added by + // PSAdd; this wider halo makes glow maps read even when the final color + // buffer is LDR and clamps the swap-chain output. + float3 bloom = 0.0; + bloom += QD3D12_LoadPostRadiance(uv) * 0.180; + + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 1.0, 0.0)) * 0.220; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-1.0, 0.0)) * 0.220; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 1.0)) * 0.220; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -1.0)) * 0.220; + + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, 2.0)) * 0.135; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, 2.0)) * 0.135; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, -2.0)) * 0.135; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, -2.0)) * 0.135; + + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 4.0, 0.0)) * 0.090; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-4.0, 0.0)) * 0.090; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 4.0)) * 0.090; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -4.0)) * 0.090; + + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 8.0, 0.0)) * 0.060; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-8.0, 0.0)) * 0.060; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 8.0)) * 0.060; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -8.0)) * 0.060; + + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 14.0, 0.0)) * 0.035; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-14.0, 0.0)) * 0.035; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 14.0)) * 0.035; + bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -14.0)) * 0.035; + + return min(bloom * 0.62, 5.0); +} + float4 PSAdd(VSOut i) : SV_Target0 { - return gTex0.Sample(gSamp0, i.uv); + float4 center = gTex0.Sample(gSamp0, i.uv); + float3 direct = max(center.rgb, 0.0); + float3 bloom = QD3D12_EmissiveBloom(i.uv); + + return float4(direct + bloom, saturate(center.a)); } float PSDepthCopy(VSOut i) : SV_Depth @@ -2531,6 +2738,7 @@ struct PSGBufferPointResolveOut float4 normal : SV_Target0; float4 position : SV_Target1; float4 velocity : SV_Target2; + float4 emissive : SV_Target3; }; PSGBufferPointResolveOut PSGBufferPointResolveMS(VSOut i) @@ -2553,6 +2761,7 @@ PSGBufferPointResolveOut PSGBufferPointResolveMS(VSOut i) o.normal = gNormalMS.Load(int2(srcPixel), sampleIndex); o.position = gPositionMS.Load(int2(srcPixel), sampleIndex); o.velocity = gVelocityMS.Load(int2(srcPixel), sampleIndex); + o.emissive = gEmissiveMS.Load(int2(srcPixel), sampleIndex); return o; } @@ -2971,6 +3180,19 @@ static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource* key.normalMapStrength = g_gl.currentNormalMapStrength; key.normalMapYSign = g_gl.currentNormalMapYSign; + TextureResource* glowMap = QD3D12_SelectGlowMapTexture(allTextures); + if (glowMap && glowMap->texture && glowMap->srvIndex != UINT_MAX) + { + key.glowMapSrvIndex = glowMap->srvIndex; + key.useGlowMap = 1.0f; + } + else + { + key.glowMapSrvIndex = g_gl.whiteTexture.srvIndex; + key.useGlowMap = 0.0f; + } + key.glowMapStrength = g_gl.currentGlowMapStrength; + key.useARBPrograms = QD3D12ARB_IsActive(); if (key.useARBPrograms) { @@ -4105,6 +4327,39 @@ static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, cl->DrawInstanced(3, 1, 0, 0); } +static void QD3D12_CompositeEmissiveIntoSceneColor(QD3D12Window& w) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + const UINT frame = w.frameIndex; + if (!cl || !g_gl.postAdditivePSO || !w.sceneColorBuffers[frame] || !w.emissiveBuffers[frame]) + return; + + QD3D12_TransitionResource(cl, w.sceneColorBuffers[frame].Get(), w.sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, w.emissiveBuffers[frame].Get(), w.emissiveBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + D3D12_VIEWPORT viewport{}; + viewport.TopLeftX = 0.0f; + viewport.TopLeftY = 0.0f; + viewport.Width = (float)w.renderWidth; + viewport.Height = (float)w.renderHeight; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + + D3D12_RECT scissor{}; + scissor.left = 0; + scissor.top = 0; + scissor.right = (LONG)w.renderWidth; + scissor.bottom = (LONG)w.renderHeight; + + QD3D12_PostFullscreenPass( + cl, + g_gl.postAdditivePSO.Get(), + w.emissiveSrvGpu[frame], + CurrentResolvedSceneColorRTV(), + viewport, + scissor); +} + static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); @@ -4773,6 +5028,25 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) } } + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + { + if (useMsaa) + { + CreateTexture2D(w.emissiveMsaaBuffers[i], w.emissiveMsaaState[i], QD3D12_EmissiveFormat, velocityClear, true, + w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, + QD3D12_RtvAt(w, QD3D12_RTV_EMISSIVE_RENDER, i), true); + CreateTexture2D(w.emissiveBuffers[i], w.emissiveBufferState[i], QD3D12_EmissiveFormat, velocityClear, false, + w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + QD3D12_RtvAt(w, QD3D12_RTV_EMISSIVE_RESOLVED, i), true); + } + else + { + CreateTexture2D(w.emissiveBuffers[i], w.emissiveBufferState[i], QD3D12_EmissiveFormat, velocityClear, true, + w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, + QD3D12_RtvAt(w, QD3D12_RTV_EMISSIVE_RENDER, i), true); + } + } + for (UINT i = 0; i < QD3D12_FrameCount; ++i) { D3D12_CPU_DESCRIPTOR_HANDLE backBufferRtv = QD3D12_RtvAt(w, QD3D12_RTV_BACKBUFFER, i); @@ -4862,6 +5136,9 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.velocityBuffers[i].Get(), QD3D12_VelocityFormat, w.velocitySrvIndex[i], w.velocitySrvCpu[i], w.velocitySrvGpu[i]); + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureSrv(w.emissiveBuffers[i].Get(), QD3D12_EmissiveFormat, w.emissiveSrvIndex[i], w.emissiveSrvCpu[i], w.emissiveSrvGpu[i]); + for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.slOutputBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.slOutputSrvIndex[i], w.slOutputSrvCpu[i], w.slOutputSrvGpu[i]); @@ -4875,6 +5152,9 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureMsaaSrv(w.velocityMsaaBuffers[i].Get(), QD3D12_VelocityFormat, w.velocityMsaaSrvIndex[i], w.velocityMsaaSrvCpu[i], w.velocityMsaaSrvGpu[i]); + + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureMsaaSrv(w.emissiveMsaaBuffers[i].Get(), QD3D12_EmissiveFormat, w.emissiveMsaaSrvIndex[i], w.emissiveMsaaSrvCpu[i], w.emissiveMsaaSrvGpu[i]); } } @@ -5196,7 +5476,7 @@ static ComPtr CompileShaderVariant(const char* entry, const char* targ static void QD3D12_CreatePostRootSignature() { - D3D12_DESCRIPTOR_RANGE ranges[5]{}; + D3D12_DESCRIPTOR_RANGE ranges[6]{}; for (UINT i = 0; i < _countof(ranges); ++i) { ranges[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; @@ -5206,7 +5486,7 @@ static void QD3D12_CreatePostRootSignature() ranges[i].OffsetInDescriptorsFromTableStart = 0; } - D3D12_ROOT_PARAMETER params[5]{}; + D3D12_ROOT_PARAMETER params[6]{}; for (UINT i = 0; i < _countof(params); ++i) { params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; @@ -5363,13 +5643,14 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( d.PrimitiveTopologyType = topoType; - d.NumRenderTargets = nativeColorOnly ? 1u : 4u; + d.NumRenderTargets = nativeColorOnly ? 1u : 5u; d.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; if (!nativeColorOnly) { d.RTVFormats[1] = DXGI_FORMAT_R16G16B16A16_FLOAT; d.RTVFormats[2] = DXGI_FORMAT_R16G16B16A16_FLOAT; d.RTVFormats[3] = QD3D12_VelocityFormat; + d.RTVFormats[4] = QD3D12_EmissiveFormat; } d.DSVFormat = QD3D12_DepthDsvFormat; @@ -5387,12 +5668,13 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( d.BlendState.RenderTarget[1].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[2].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[3].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[4].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; } d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = nativeColorOnly ? FALSE : TRUE; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < (nativeColorOnly ? 1 : 5); ++i) { auto& rt = d.BlendState.RenderTarget[i]; rt.BlendEnable = FALSE; @@ -5500,16 +5782,17 @@ static void QD3D12_CreatePSOs() gbufferResolveDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; gbufferResolveDesc.SampleDesc.Count = 1; gbufferResolveDesc.SampleMask = UINT_MAX; - gbufferResolveDesc.NumRenderTargets = 3; + gbufferResolveDesc.NumRenderTargets = 4; gbufferResolveDesc.RTVFormats[0] = DXGI_FORMAT_R16G16B16A16_FLOAT; gbufferResolveDesc.RTVFormats[1] = DXGI_FORMAT_R16G16B16A16_FLOAT; gbufferResolveDesc.RTVFormats[2] = QD3D12_VelocityFormat; + gbufferResolveDesc.RTVFormats[3] = QD3D12_EmissiveFormat; gbufferResolveDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; gbufferResolveDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; gbufferResolveDesc.RasterizerState.DepthClipEnable = TRUE; gbufferResolveDesc.BlendState.AlphaToCoverageEnable = FALSE; gbufferResolveDesc.BlendState.IndependentBlendEnable = FALSE; - for (UINT rt = 0; rt < 3; ++rt) + for (UINT rt = 0; rt < 4; ++rt) { gbufferResolveDesc.BlendState.RenderTarget[rt].BlendEnable = FALSE; gbufferResolveDesc.BlendState.RenderTarget[rt].LogicOpEnable = FALSE; @@ -5628,13 +5911,14 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( d.InputLayout.NumElements = _countof(kGLVertexInputLayoutARB); d.PrimitiveTopologyType = topoType; - d.NumRenderTargets = nativeColorOnly ? 1u : 4u; + d.NumRenderTargets = nativeColorOnly ? 1u : 5u; d.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; if (!nativeColorOnly) { d.RTVFormats[1] = DXGI_FORMAT_R16G16B16A16_FLOAT; d.RTVFormats[2] = DXGI_FORMAT_R16G16B16A16_FLOAT; d.RTVFormats[3] = QD3D12_VelocityFormat; + d.RTVFormats[4] = QD3D12_EmissiveFormat; } d.DSVFormat = QD3D12_DepthDsvFormat; @@ -5649,7 +5933,7 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = nativeColorOnly ? FALSE : TRUE; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < (nativeColorOnly ? 1 : 5); ++i) { auto& rt = d.BlendState.RenderTarget[i]; rt.BlendEnable = FALSE; @@ -6023,9 +6307,11 @@ void QD3D12_BeginFrame() const float normalClear[4] = { 0.0f, 0.0f, 1.0f, 0.5f }; const float positionClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; const float velocityClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + const float emissiveClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; g_gl.cmdList->ClearRenderTargetView(CurrentNormalRTV(), normalClear, 0, nullptr); g_gl.cmdList->ClearRenderTargetView(CurrentPositionRTV(), positionClear, 0, nullptr); g_gl.cmdList->ClearRenderTargetView(CurrentVelocityRTV(), velocityClear, 0, nullptr); + g_gl.cmdList->ClearRenderTargetView(CurrentEmissiveRTV(), emissiveClear, 0, nullptr); g_gl.frameOpen = true; g_gl.frameOwner = &w; @@ -6053,6 +6339,8 @@ void QD3D12_EndFrame() if (!g_gl.sceneResolvedThisFrame) { QD3D12_ResolveGBufferForCurrentFrame(w); + if (!g_gl.raytracedLightingReadyThisFrame) + QD3D12_CompositeEmissiveIntoSceneColor(w); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.sceneColorBuffers[w.frameIndex].Get(), w.sceneColorState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.normalBuffers[w.frameIndex].Get(), w.normalBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); @@ -7544,8 +7832,12 @@ static void FlushImmediate(GLenum mode, const GLVertex* src, size_t n) const bool explicitNormalUnit = (!arbProgramsActive) && (g_gl.currentNormalMapTexture != 0) && (g_gl.boundTexture[unit] == g_gl.currentNormalMapTexture); + const bool taggedGlowUnit = (!arbProgramsActive) && QD3D12_IsTextureTaggedGlowMap(g_gl.boundTexture[unit]); + const bool explicitGlowUnit = (!arbProgramsActive) && + (g_gl.currentGlowMapTexture != 0) && + (g_gl.boundTexture[unit] == g_gl.currentGlowMapTexture); - const bool wantsUnit = arbUnit || fixedColorUnit || taggedNormalUnit || explicitNormalUnit; + const bool wantsUnit = arbUnit || fixedColorUnit || taggedNormalUnit || explicitNormalUnit || taggedGlowUnit || explicitGlowUnit; if (!wantsUnit) continue; @@ -7571,6 +7863,13 @@ static void FlushImmediate(GLenum mode, const GLVertex* src, size_t n) UploadTexture(*normalMapTex); } + TextureResource* glowMapTex = QD3D12_SelectGlowMapTexture(boundTextures); + if (glowMapTex && glowMapTex != &g_gl.whiteTexture && !glowMapTex->gpuValid) + { + EnsureTextureResource(*glowMapTex); + UploadTexture(*glowMapTex); + } + TextureResource* tex0 = boundTextures[0]; TextureResource* tex1 = boundTextures[1]; @@ -7876,9 +8175,11 @@ static void QD3D12_FlushQueuedBatches() D3D12_GPU_DESCRIPTOR_HANDLE lastTex0{}; D3D12_GPU_DESCRIPTOR_HANDLE lastTex1{}; D3D12_GPU_DESCRIPTOR_HANDLE lastNormalMap{}; + D3D12_GPU_DESCRIPTOR_HANDLE lastGlowMap{}; bool haveLastTex0 = false; bool haveLastTex1 = false; bool haveLastNormalMap = false; + bool haveLastGlowMap = false; D3D12_PRIMITIVE_TOPOLOGY lastTopo = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; for (size_t i = 0; i < g_gl.queuedBatches.size(); ++i) @@ -7904,6 +8205,7 @@ static void QD3D12_FlushQueuedBatches() haveLastTex0 = false; haveLastTex1 = false; haveLastNormalMap = false; + haveLastGlowMap = false; lastTopo = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; } @@ -7971,6 +8273,7 @@ static void QD3D12_FlushQueuedBatches() dc->_motionPad[0] = batch.key.useNormalMap; dc->_motionPad[1] = batch.key.normalMapStrength; dc->_motionPad[2] = batch.key.normalMapYSign; + dc->_motionPad[3] = (batch.key.useGlowMap > 0.5f) ? batch.key.glowMapStrength : 0.0f; if (batch.key.useARBPrograms) { @@ -8026,6 +8329,7 @@ static void QD3D12_FlushQueuedBatches() haveLastTex0 = false; haveLastTex1 = false; haveLastNormalMap = false; + haveLastGlowMap = false; } else { @@ -8062,6 +8366,18 @@ static void QD3D12_FlushQueuedBatches() lastNormalMap = normalMapGpu; haveLastNormalMap = true; } + + UINT glowSrvIndex = batch.key.glowMapSrvIndex; + if (glowSrvIndex == UINT_MAX) + glowSrvIndex = g_gl.whiteTexture.srvIndex; + + D3D12_GPU_DESCRIPTOR_HANDLE glowMapGpu = QD3D12_SrvGpu(glowSrvIndex); + if (!haveLastGlowMap || glowMapGpu.ptr != lastGlowMap.ptr) + { + g_gl.cmdList->SetGraphicsRootDescriptorTable(4, glowMapGpu); + lastGlowMap = glowMapGpu; + haveLastGlowMap = true; + } } g_gl.cmdList->SetGraphicsRootConstantBufferView(0, cbAlloc.gpu); @@ -8192,11 +8508,13 @@ void APIENTRY glClear(GLbitfield mask) const float normalClear[4] = { 0.0f, 0.0f, 1.0f, 0.5f }; const float positionClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; const float velocityClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + const float emissiveClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; g_gl.cmdList->ClearRenderTargetView(CurrentRTV(), cc, 1, &clearRect); g_gl.cmdList->ClearRenderTargetView(CurrentNormalRTV(), normalClear, 1, &clearRect); g_gl.cmdList->ClearRenderTargetView(CurrentPositionRTV(), positionClear, 1, &clearRect); g_gl.cmdList->ClearRenderTargetView(CurrentVelocityRTV(), velocityClear, 1, &clearRect); + g_gl.cmdList->ClearRenderTargetView(CurrentEmissiveRTV(), emissiveClear, 1, &clearRect); } } @@ -8543,6 +8861,9 @@ void APIENTRY glDeleteTextures(GLsizei n, const GLuint* textures) if (g_gl.currentNormalMapTexture == deadId) g_gl.currentNormalMapTexture = 0; + if (g_gl.currentGlowMapTexture == deadId) + g_gl.currentGlowMapTexture = 0; + auto it = g_gl.textures.find(deadId); if (it != g_gl.textures.end()) { @@ -8606,6 +8927,49 @@ void APIENTRY glNormalMapYSignf(GLfloat sign) g_gl.currentNormalMapYSign = (sign < 0.0f) ? -1.0f : 1.0f; } +void APIENTRY glTagTextureGlowMap(GLuint texture, GLboolean isGlowMap) +{ + if (texture == 0) + { + g_gl.lastError = GL_INVALID_VALUE; + return; + } + + TextureResource& tex = QD3D12_EnsureTextureName(texture); + tex.isGlowMap = (isGlowMap != GL_FALSE); +} + +void APIENTRY glTextureGlowMap(GLuint texture, GLboolean isGlowMap) +{ + glTagTextureGlowMap(texture, isGlowMap); +} + +void APIENTRY glBindGlowMapTexture(GLuint texture) +{ + if (texture != 0) + { + TextureResource& tex = QD3D12_EnsureTextureName(texture); + if (tex.isNormalMap && !tex.isGlowMap) + { + QD3D12_Log("glBindGlowMapTexture(%u) ignored: texture is tagged as a normal map. Did you pass the bump image texnum instead of the glow image texnum?", texture); + g_gl.currentGlowMapTexture = 0; + return; + } + } + + g_gl.currentGlowMapTexture = texture; +} + +void APIENTRY glGlowMapTexture(GLuint texture) +{ + glBindGlowMapTexture(texture); +} + +void APIENTRY glGlowMapStrengthf(GLfloat strength) +{ + g_gl.currentGlowMapStrength = (strength < 0.0f) ? 0.0f : strength; +} + void APIENTRY glRaytracingMaterialFlagsQD3D12(GLuint flags) { g_gl.currentRayMaterialFlags = QD3D12_ClampRayMaterialFlags((uint32_t)flags); @@ -8742,6 +9106,10 @@ void APIENTRY glGetIntegerv(GLenum pname, GLint* params) *params = (GLint)g_gl.currentNormalMapTexture; break; + case GL_GLOW_MAP_BINDING_QD3D12: + *params = (GLint)g_gl.currentGlowMapTexture; + break; + case GL_QD3D12_MATERIAL_FLAGS: *params = (GLint)QD3D12_CurrentRayMaterialFlags(); break; @@ -11301,6 +11669,11 @@ PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { { "glNormalMapTexture", (PROC)glNormalMapTexture }, { "glNormalMapStrengthf", (PROC)glNormalMapStrengthf }, { "glNormalMapYSignf", (PROC)glNormalMapYSignf }, + { "glTagTextureGlowMap", (PROC)glTagTextureGlowMap }, + { "glTextureGlowMap", (PROC)glTextureGlowMap }, + { "glBindGlowMapTexture", (PROC)glBindGlowMapTexture }, + { "glGlowMapTexture", (PROC)glGlowMapTexture }, + { "glGlowMapStrengthf", (PROC)glGlowMapStrengthf }, { "glGlassMaterialQD3D12", (PROC)glGlassMaterialQD3D12 }, { "glMaterialGlassQD3D12", (PROC)glMaterialGlassQD3D12 }, { "glRaytracingMaterialFlagsQD3D12", (PROC)glRaytracingMaterialFlagsQD3D12 }, @@ -11560,8 +11933,8 @@ static void QD3D12_PointResolveMsaaGBufferToSingleSample(QD3D12Window& w) const UINT frame = w.frameIndex; if (!w.depthMsaaBuffer || - !w.normalMsaaBuffers[frame] || !w.positionMsaaBuffers[frame] || !w.velocityMsaaBuffers[frame] || - !w.normalBuffers[frame] || !w.positionBuffers[frame] || !w.velocityBuffers[frame]) + !w.normalMsaaBuffers[frame] || !w.positionMsaaBuffers[frame] || !w.velocityMsaaBuffers[frame] || !w.emissiveMsaaBuffers[frame] || + !w.normalBuffers[frame] || !w.positionBuffers[frame] || !w.velocityBuffers[frame] || !w.emissiveBuffers[frame]) { return; } @@ -11570,16 +11943,19 @@ static void QD3D12_PointResolveMsaaGBufferToSingleSample(QD3D12Window& w) QD3D12_TransitionResource(cl, w.normalMsaaBuffers[frame].Get(), w.normalMsaaState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.positionMsaaBuffers[frame].Get(), w.positionMsaaState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.velocityMsaaBuffers[frame].Get(), w.velocityMsaaState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + QD3D12_TransitionResource(cl, w.emissiveMsaaBuffers[frame].Get(), w.emissiveMsaaState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.normalBuffers[frame].Get(), w.normalBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.positionBuffers[frame].Get(), w.positionBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.velocityBuffers[frame].Get(), w.velocityBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, w.emissiveBuffers[frame].Get(), w.emissiveBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); - D3D12_CPU_DESCRIPTOR_HANDLE rtvs[3] = + D3D12_CPU_DESCRIPTOR_HANDLE rtvs[4] = { CurrentResolvedNormalRTV(), CurrentResolvedPositionRTV(), - CurrentResolvedVelocityRTV() + CurrentResolvedVelocityRTV(), + CurrentResolvedEmissiveRTV() }; D3D12_VIEWPORT viewport{}; @@ -11600,17 +11976,18 @@ static void QD3D12_PointResolveMsaaGBufferToSingleSample(QD3D12Window& w) cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); cl->SetPipelineState(g_gl.postGBufferPointResolveMsaaPSO.Get()); - cl->OMSetRenderTargets(3, rtvs, FALSE, nullptr); + cl->OMSetRenderTargets(4, rtvs, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - // t1..t4: depth + discontinuous G-buffer attributes. The shader uses + // t1..t5: depth + discontinuous G-buffer attributes. The shader uses // Texture2DMS.Load(), selects a single depth-nearest sample, and never averages. cl->SetGraphicsRootDescriptorTable(1, w.depthMsaaSrvGpu); cl->SetGraphicsRootDescriptorTable(2, w.normalMsaaSrvGpu[frame]); cl->SetGraphicsRootDescriptorTable(3, w.positionMsaaSrvGpu[frame]); cl->SetGraphicsRootDescriptorTable(4, w.velocityMsaaSrvGpu[frame]); + cl->SetGraphicsRootDescriptorTable(5, w.emissiveMsaaSrvGpu[frame]); cl->DrawInstanced(3, 1, 0, 0); } @@ -11686,6 +12063,7 @@ static void QD3D12_BindLowResSceneTargets(QD3D12Window& w) QD3D12_TransitionResource(cl, w.normalMsaaBuffers[w.frameIndex].Get(), w.normalMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.positionMsaaBuffers[w.frameIndex].Get(), w.positionMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.velocityMsaaBuffers[w.frameIndex].Get(), w.velocityMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, w.emissiveMsaaBuffers[w.frameIndex].Get(), w.emissiveMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.depthMsaaBuffer.Get(), w.depthMsaaState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } else @@ -11694,18 +12072,20 @@ static void QD3D12_BindLowResSceneTargets(QD3D12Window& w) QD3D12_TransitionResource(cl, w.normalBuffers[w.frameIndex].Get(), w.normalBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.positionBuffers[w.frameIndex].Get(), w.positionBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.velocityBuffers[w.frameIndex].Get(), w.velocityBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, w.emissiveBuffers[w.frameIndex].Get(), w.emissiveBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.depthBuffer.Get(), w.depthState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } - D3D12_CPU_DESCRIPTOR_HANDLE rtvs[4] = + D3D12_CPU_DESCRIPTOR_HANDLE rtvs[5] = { CurrentRTV(), CurrentNormalRTV(), CurrentPositionRTV(), - CurrentVelocityRTV() + CurrentVelocityRTV(), + CurrentEmissiveRTV() }; D3D12_CPU_DESCRIPTOR_HANDLE dsv = CurrentActiveSceneDepthDSV(); - cl->OMSetRenderTargets(4, rtvs, FALSE, &dsv); + cl->OMSetRenderTargets(5, rtvs, FALSE, &dsv); cl->RSSetViewports(1, &w.viewport); cl->RSSetScissorRects(1, &w.scissor); } @@ -11795,6 +12175,8 @@ static void QD3D12_ResolveSceneToOutputAndEnterNativePhase(QD3D12Window& w) QD3D12_ResolveGBufferForCurrentFrame(w); const bool useLightingUpscaleInput = QD3D12_UseLightingTextureAsUpscaleInput(w); + if (!g_gl.raytracedLightingReadyThisFrame) + QD3D12_CompositeEmissiveIntoSceneColor(w); QD3D12_TransitionResource(cl, w.sceneColorBuffers[w.frameIndex].Get(), w.sceneColorState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.velocityBuffers[w.frameIndex].Get(), w.velocityBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); @@ -11862,9 +12244,10 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) ID3D12Resource* sceneColor = window->sceneColorBuffers[frameIndex].Get(); ID3D12Resource* sceneNormal = window->normalBuffers[frameIndex].Get(); ID3D12Resource* scenePosition = window->positionBuffers[frameIndex].Get(); + ID3D12Resource* sceneEmissive = window->emissiveBuffers[frameIndex].Get(); ID3D12Resource* sceneDepth = window->depthBuffer.Get(); - if (!sceneColor || !sceneNormal || !scenePosition || !sceneDepth) + if (!sceneColor || !sceneNormal || !scenePosition || !sceneEmissive || !sceneDepth) return; QD3D12_TransitionResource( @@ -11885,6 +12268,12 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) window->positionBufferState[frameIndex], D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + QD3D12_TransitionResource( + cl, + sceneEmissive, + window->emissiveBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + QD3D12_TransitionResource( cl, sceneDepth, @@ -11926,6 +12315,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) activeMaxBounces, useDLSSRayReconstruction ? 0 : 1, useDLSSRayReconstruction ? 0.0f : 1.0f); + glRaytracingLightingSetEmissiveInput(sceneEmissive, QD3D12_EmissiveFormat); glRaytracingLightingPassDesc_t pass = {}; pass.albedoTexture = sceneColor; @@ -11954,6 +12344,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) QD3D12_TransitionResource(cl, window->normalMsaaBuffers[frameIndex].Get(), window->normalMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, window->positionMsaaBuffers[frameIndex].Get(), window->positionMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, window->velocityMsaaBuffers[frameIndex].Get(), window->velocityMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, window->emissiveMsaaBuffers[frameIndex].Get(), window->emissiveMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, window->depthMsaaBuffer.Get(), window->depthMsaaState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } else @@ -11976,6 +12367,12 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) window->positionBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource( + cl, + sceneEmissive, + window->emissiveBufferState[frameIndex], + D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource( cl, sceneDepth, diff --git a/neo/opengl/opengl.h b/neo/opengl/opengl.h index f6478592..fd79f424 100644 --- a/neo/opengl/opengl.h +++ b/neo/opengl/opengl.h @@ -2254,4 +2254,17 @@ extern "C" { } #endif -void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); \ No newline at end of file +void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); + +// QD3D12 glow-map extension additions for opengl.h +#ifndef GL_GLOW_MAP_BINDING_QD3D12 +#define GL_GLOW_MAP_BINDING_QD3D12 0x6005 +#endif + +// Glow-map material tagging / binding. Tagging is optional but lets the shim +// auto-discover a glow map from bound texture units. Explicit binding wins. +void APIENTRY glTagTextureGlowMap(GLuint texture, GLboolean isGlowMap); +void APIENTRY glTextureGlowMap(GLuint texture, GLboolean isGlowMap); // alias +void APIENTRY glBindGlowMapTexture(GLuint texture); // 0 disables explicit glow map +void APIENTRY glGlowMapTexture(GLuint texture); // alias +void APIENTRY glGlowMapStrengthf(GLfloat strength); // default 1.0; >1 allows overbright emission diff --git a/neo/renderer/Material.cpp b/neo/renderer/Material.cpp index 1e14c6c0..a10dd520 100644 --- a/neo/renderer/Material.cpp +++ b/neo/renderer/Material.cpp @@ -931,6 +931,11 @@ void idMaterial::ParseBlend( idLexer &src, shaderStage_t *stage ) { stage->lighting = SL_DIFFUSE; return; } + if (!token.Icmp("glowmap")) { + stage->lighting = SL_GLOWMAP; + return; + } + if ( !token.Icmp( "specularmap" ) ) { stage->lighting = SL_SPECULAR; return; @@ -1895,6 +1900,20 @@ idImage* idMaterial::GetBumpImage(void) const { return GetEditorImage(); } +/* +=================== +idMaterial::GetGlowImage +=================== +*/ +idImage* idMaterial::GetGlowImage(void) const { + for (int i = 0; i < numStages; i++) { + if ((stages[i].lighting == SL_GLOWMAP || stages[i].isGlow) && stages[i].texture.image) { + return stages[i].texture.image; + } + } + return NULL; +} + /* ============== idMaterial::AddImplicitStages @@ -2254,12 +2273,18 @@ void idMaterial::ParseMaterial(idLexer& src) { src.ParseRestOfLine(renderBump); continue; } -#ifdef PREY + else if (!token.Icmp("glowmap")) { - src.ReadTokenOnLine(&token); + str = R_ParsePastImageProgram(src); + idStr::snPrintf(buffer, sizeof(buffer), "blend glowmap\nmap %s\n}\n", str); + newSrc.LoadMemory(buffer, strlen(buffer), "glowmap"); + newSrc.SetFlags(LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES); + ParseStage(newSrc, trpDefault); + newSrc.FreeSource(); continue; } +#ifdef PREY else if (!token.Icmp("highres")) { continue; } diff --git a/neo/renderer/Material.h b/neo/renderer/Material.h index 26337831..beb610a1 100644 --- a/neo/renderer/Material.h +++ b/neo/renderer/Material.h @@ -194,7 +194,8 @@ typedef enum { SL_AMBIENT, // execute after lighting SL_BUMP, SL_DIFFUSE, - SL_SPECULAR + SL_SPECULAR, + SL_GLOWMAP #ifdef PREY , SL_SHADER #endif @@ -506,6 +507,7 @@ public: // jmarshall idImage* GetDiffuseImage(void) const; idImage* GetBumpImage(void) const; + idImage* GetGlowImage(void) const; bool IsSky(void) const; // jmarshall end // returns true if the material will generate interactions with normal lights diff --git a/neo/renderer/draw_common.cpp b/neo/renderer/draw_common.cpp index f1981925..bb876039 100644 --- a/neo/renderer/draw_common.cpp +++ b/neo/renderer/draw_common.cpp @@ -512,6 +512,16 @@ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { shader->GetBumpImage()->Bind(); glBindNormalMapTexture(shader->GetBumpImage()->texnum); + idImage* glowMapImage = shader->GetGlowImage(); + if (glowMapImage) + { + GL_SelectTexture(2); + glEnable(GL_TEXTURE_2D); + glowMapImage->Bind(); + glBindGlowMapTexture(glowMapImage->texnum); + glGlowMapStrengthf(0.5f); + } + if (!surf->geo->isSkeletal) { glGeometryFlagf(GEOMETRY_FLAG_NONE); @@ -527,9 +537,15 @@ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { // draw it RB_DrawElementsWithCounters(tri); + if (glowMapImage) + { + GL_SelectTexture(2); + glBindGlowMapTexture(0); + globalImages->BindNull(); + } - glBindNormalMapTexture(0); GL_SelectTexture(1); + glBindNormalMapTexture(0); globalImages->BindNull(); GL_SelectTexture(0); } @@ -1073,205 +1089,6 @@ int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs ) { return i; } - - -/* -============================================================================== - -BACK END RENDERING OF STENCIL SHADOWS - -============================================================================== -*/ - -/* -===================== -RB_T_Shadow - -the shadow volumes face INSIDE -===================== -*/ -static void RB_T_Shadow( const drawSurf_t *surf ) { - const srfTriangles_t *tri; - - // set the light position if we are using a vertex program to project the rear surfaces - if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() - && surf->space != backEnd.currentSpace ) { - idVec4 localLight; - - R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.vLight->globalLightOrigin, localLight.ToVec3() ); - localLight.w = 0.0f; - glProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, localLight.ToFloatPtr() ); - } - - tri = surf->geo; - - if ( !tri->shadowCache ) { - return; - } - - glVertexPointer( 4, GL_FLOAT, sizeof( shadowCache_t ), vertexCache.Position(tri->shadowCache) ); - - // we always draw the sil planes, but we may not need to draw the front or rear caps - int numIndexes; - bool external = false; - - if ( !r_useExternalShadows.GetInteger() ) { - numIndexes = tri->numIndexes; - } else if ( r_useExternalShadows.GetInteger() == 2 ) { // force to no caps for testing - numIndexes = tri->numShadowIndexesNoCaps; - } else if ( !(surf->dsFlags & DSF_VIEW_INSIDE_SHADOW) ) { - // if we aren't inside the shadow projection, no caps are ever needed needed - numIndexes = tri->numShadowIndexesNoCaps; - external = true; - } else if ( !backEnd.vLight->viewInsideLight && !(surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE) ) { - // if we are inside the shadow projection, but outside the light, and drawing - // a non-infinite shadow, we can skip some caps - if ( backEnd.vLight->viewSeesShadowPlaneBits & surf->geo->shadowCapPlaneBits ) { - // we can see through a rear cap, so we need to draw it, but we can skip the - // caps on the actual surface - numIndexes = tri->numShadowIndexesNoFrontCaps; - } else { - // we don't need to draw any caps - numIndexes = tri->numShadowIndexesNoCaps; - } - external = true; - } else { - // must draw everything - numIndexes = tri->numIndexes; - } - - // set depth bounds - if( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { - glDepthBoundsEXT( surf->scissorRect.zmin, surf->scissorRect.zmax ); - } - - // debug visualization - if ( r_showShadows.GetInteger() ) { - if ( r_showShadows.GetInteger() == 3 ) { - if ( external ) { - glColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); - } else { - // these are the surfaces that require the reverse - glColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); - } - } else { - // draw different color for turboshadows - if ( surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE ) { - if ( numIndexes == tri->numIndexes ) { - glColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); - } else { - glColor3f( 1/backEnd.overBright, 0.4/backEnd.overBright, 0.1/backEnd.overBright ); - } - } else { - if ( numIndexes == tri->numIndexes ) { - glColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); - } else if ( numIndexes == tri->numShadowIndexesNoFrontCaps ) { - glColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.6/backEnd.overBright ); - } else { - glColor3f( 0.6/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); - } - } - } - - glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); - glDisable( GL_STENCIL_TEST ); - GL_Cull( CT_TWO_SIDED ); - RB_DrawShadowElementsWithCounters( tri, numIndexes ); - GL_Cull( CT_FRONT_SIDED ); - glEnable( GL_STENCIL_TEST ); - - return; - } - - // patent-free work around - if ( !external ) { - // "preload" the stencil buffer with the number of volumes - // that get clipped by the near or far clip plane - glStencilOp( GL_KEEP, tr.stencilDecr, tr.stencilDecr ); - GL_Cull( CT_FRONT_SIDED ); - RB_DrawShadowElementsWithCounters( tri, numIndexes ); - glStencilOp( GL_KEEP, tr.stencilIncr, tr.stencilIncr ); - GL_Cull( CT_BACK_SIDED ); - RB_DrawShadowElementsWithCounters( tri, numIndexes ); - } - - // traditional depth-pass stencil shadows - glStencilOp( GL_KEEP, GL_KEEP, tr.stencilIncr ); - GL_Cull( CT_FRONT_SIDED ); - RB_DrawShadowElementsWithCounters( tri, numIndexes ); - - glStencilOp( GL_KEEP, GL_KEEP, tr.stencilDecr ); - GL_Cull( CT_BACK_SIDED ); - RB_DrawShadowElementsWithCounters( tri, numIndexes ); -} - -/* -===================== -RB_StencilShadowPass - -Stencil test should already be enabled, and the stencil buffer should have -been set to 128 on any surfaces that might receive shadows -===================== -*/ -void RB_StencilShadowPass( const drawSurf_t *drawSurfs ) { - if ( !r_shadows.GetBool() ) { - return; - } - - if ( !drawSurfs ) { - return; - } - - RB_LogComment( "---------- RB_StencilShadowPass ----------\n" ); - - globalImages->BindNull(); - glDisableClientState( GL_TEXTURE_COORD_ARRAY ); - - // for visualizing the shadows - if ( r_showShadows.GetInteger() ) { - if ( r_showShadows.GetInteger() == 2 ) { - // draw filled in - GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_LESS ); - } else { - // draw as lines, filling the depth buffer - GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); - } - } else { - // don't write to the color buffer, just the stencil buffer - GL_State( GLS_DEPTHMASK | GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHFUNC_LESS ); - } - - if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { - glPolygonOffset( r_shadowPolygonFactor.GetFloat(), -r_shadowPolygonOffset.GetFloat() ); - glEnable( GL_POLYGON_OFFSET_FILL ); - } - - glStencilFunc( GL_ALWAYS, 1, 255 ); - - if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { - glEnable( GL_DEPTH_BOUNDS_TEST_EXT ); - } - - RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_Shadow ); - - GL_Cull( CT_FRONT_SIDED ); - - if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { - glDisable( GL_POLYGON_OFFSET_FILL ); - } - - if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { - glDisable( GL_DEPTH_BOUNDS_TEST_EXT ); - } - - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - - glStencilFunc( GL_GEQUAL, 128, 255 ); - glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); -} - - - /* ============================================================================================= @@ -1627,76 +1444,6 @@ void RB_STD_FogAllLights( void ) { //========================================================================================= -/* -================== -RB_STD_LightScale - -Perform extra blending passes to multiply the entire buffer by -a floating point value -================== -*/ -void RB_STD_LightScale( void ) { - float v, f; - - if ( backEnd.overBright == 1.0f ) { - return; - } - - if ( r_skipLightScale.GetBool() ) { - return; - } - - RB_LogComment( "---------- RB_STD_LightScale ----------\n" ); - - // the scissor may be smaller than the viewport for subviews - if ( r_useScissor.GetBool() ) { - glScissor( backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, - backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, - backEnd.viewDef->scissor.x2 - backEnd.viewDef->scissor.x1 + 1, - backEnd.viewDef->scissor.y2 - backEnd.viewDef->scissor.y1 + 1 ); - backEnd.currentScissor = backEnd.viewDef->scissor; - } - - // full screen blends - glLoadIdentity(); - glMatrixMode( GL_PROJECTION ); - glPushMatrix(); - glLoadIdentity(); - glOrtho( 0, 1, 0, 1, -1, 1 ); - - GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR ); - GL_Cull( CT_TWO_SIDED ); // so mirror views also get it - globalImages->BindNull(); - glDisable( GL_DEPTH_TEST ); - glDisable( GL_STENCIL_TEST ); - - v = 1; - while ( idMath::Fabs( v - backEnd.overBright ) > 0.01 ) { // a little extra slop - f = backEnd.overBright / v; - f /= 2; - if ( f > 1 ) { - f = 1; - } - glColor3f( f, f, f ); - v = v * f * 2; - - glBegin( GL_QUADS ); - glVertex2f( 0,0 ); - glVertex2f( 0,1 ); - glVertex2f( 1,1 ); - glVertex2f( 1,0 ); - glEnd(); - } - - - glPopMatrix(); - glEnable( GL_DEPTH_TEST ); - glMatrixMode( GL_MODELVIEW ); - GL_Cull( CT_FRONT_SIDED ); -} - -//========================================================================================= - /* ============= RB_STD_DrawView @@ -1717,9 +1464,6 @@ void RB_STD_DrawView( void ) { // clear the z buffer, set the projection matrix, etc RB_BeginDrawingView(); - // decide how much overbrighting we are going to do - RB_DetermineLightScale(); - // fill the depth buffer and clear color buffer to black except on // subviews RB_STD_FillDepthBuffer( drawSurfs, numDrawSurfs ); @@ -1730,9 +1474,6 @@ void RB_STD_DrawView( void ) { // disable stencil shadow test glStencilFunc( GL_ALWAYS, 128, 255 ); - // uplight the entire screen to crutch up not having better blending range - RB_STD_LightScale(); - // now draw any non-light dependent shading passes int processed = RB_STD_DrawShaderPasses( drawSurfs, numDrawSurfs );