/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IceTech Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IceTech Source Code. If not, see . If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ // gl_d3d12shim.cpp // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Microsoft::WRL::ComPtr; #include #include #include #define QD3D12_ENABLE_STREAMLINE #if defined(QD3D12_ENABLE_STREAMLINE) #include #include #include #include #endif #if defined(QD3D12_ENABLE_FFX) #include #include #include #include #endif using Microsoft::WRL::ComPtr; #include "opengl.h" #include "gl_d3d12arb.h" #ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 #endif #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #endif #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #endif #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #endif #ifndef GL_COMPRESSED_ALPHA_ARB #define GL_COMPRESSED_ALPHA_ARB 0x84E9 #endif #ifndef GL_COMPRESSED_LUMINANCE_ARB #define GL_COMPRESSED_LUMINANCE_ARB 0x84EA #endif #ifndef GL_COMPRESSED_LUMINANCE_ALPHA_ARB #define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB #endif #ifndef GL_COMPRESSED_INTENSITY_ARB #define GL_COMPRESSED_INTENSITY_ARB 0x84EC #endif #ifndef GL_COMPRESSED_RGB_ARB #define GL_COMPRESSED_RGB_ARB 0x84ED #endif #ifndef GL_COMPRESSED_RGBA_ARB #define GL_COMPRESSED_RGBA_ARB 0x84EE #endif #ifndef GL_TEXTURE_COMPRESSION_HINT_ARB #define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF #endif #ifndef GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB #define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 #endif #ifndef GL_TEXTURE_COMPRESSED_ARB #define GL_TEXTURE_COMPRESSED_ARB 0x86A1 #endif #ifndef GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB #define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 #endif #ifndef GL_COMPRESSED_TEXTURE_FORMATS_ARB #define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 #endif #ifndef GL_TEXTURE_WIDTH #define GL_TEXTURE_WIDTH 0x1000 #endif #ifndef GL_TEXTURE_HEIGHT #define GL_TEXTURE_HEIGHT 0x1001 #endif #ifndef GL_TEXTURE_INTERNAL_FORMAT #define GL_TEXTURE_INTERNAL_FORMAT 0x1003 #endif #ifndef GL_POLYGON_OFFSET_UNITS #define GL_POLYGON_OFFSET_UNITS 0x2A00 #endif #ifndef GL_POLYGON_OFFSET_POINT #define GL_POLYGON_OFFSET_POINT 0x2A01 #endif #ifndef GL_POLYGON_OFFSET_LINE #define GL_POLYGON_OFFSET_LINE 0x2A02 #endif #ifndef GL_POLYGON_OFFSET_FILL #define GL_POLYGON_OFFSET_FILL 0x8037 #endif #ifndef GL_POLYGON_OFFSET_FACTOR #define GL_POLYGON_OFFSET_FACTOR 0x8038 #endif // QD3D12-private compatibility enums used by the optional tangent-space // normal-map path. They intentionally live outside the official GL enum range // used by this shim and are only consumed by this translation layer. #ifndef GL_TANGENT_ARRAY_QD3D12 #define GL_TANGENT_ARRAY_QD3D12 0x6000 #endif #ifndef GL_BINORMAL_ARRAY_QD3D12 #define GL_BINORMAL_ARRAY_QD3D12 0x6001 #endif #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 #ifndef GL_SPECULAR_MAP_BINDING_QD3D12 #define GL_SPECULAR_MAP_BINDING_QD3D12 0x6006 #endif #ifndef GL_QD3D12_UPSCALER_BACKEND #define GL_QD3D12_UPSCALER_BACKEND 0x6007 #endif #ifndef GL_QD3D12_UPSCALER_QUALITY #define GL_QD3D12_UPSCALER_QUALITY 0x6008 #endif #ifndef GL_QD3D12_DLAA_ENABLED #define GL_QD3D12_DLAA_ENABLED 0x6009 #endif #ifndef GL_QD3D12_TONEMAP_BRIGHTNESS #define GL_QD3D12_TONEMAP_BRIGHTNESS 0x600A #endif #ifndef GL_QD3D12_TAA_ENABLED #define GL_QD3D12_TAA_ENABLED 0x600B #endif #ifndef GL_QD3D12_NEURAL_POM_ENABLED #define GL_QD3D12_NEURAL_POM_ENABLED 0x600C #endif #ifndef GL_QD3D12_NEURAL_POM_BINDING #define GL_QD3D12_NEURAL_POM_BINDING 0x600D #endif #ifndef GL_QD3D12_TAA #define GL_QD3D12_TAA GL_QD3D12_TAA_ENABLED #endif #ifndef GL_QD3D12_TONE_MAP_BRIGHTNESS #define GL_QD3D12_TONE_MAP_BRIGHTNESS GL_QD3D12_TONEMAP_BRIGHTNESS #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. #ifndef GL_QD3D12_MATERIAL_GLASS #define GL_QD3D12_MATERIAL_GLASS 0x6003 #endif #ifndef GL_QD3D12_MATERIAL_FLAGS #define GL_QD3D12_MATERIAL_FLAGS 0x6004 #endif #ifndef GL_RAYTRACING_MATERIAL_FLAG_GLASS #define GL_RAYTRACING_MATERIAL_FLAG_GLASS 0x00000001u #endif #ifndef GL_RAYTRACING_MATERIAL_FLAG_MASK_QD3D12 #define GL_RAYTRACING_MATERIAL_FLAG_MASK_QD3D12 0x000000FFu #endif static constexpr uint32_t QD3D12_RT_INSTANCE_USER_ID_MASK = 0x0000FFFFu; static constexpr uint32_t QD3D12_RT_INSTANCE_MATERIAL_SHIFT = 16u; static constexpr uint32_t QD3D12_GEOMETRY_FLAG_GLASS_BIT = 4u; static constexpr float QD3D12_MATERIAL_TYPE_GLASS = 3.0f; #ifndef GL_RGB_S3TC #define GL_RGB_S3TC 0x83A0 #endif #ifndef GL_RGB4_S3TC #define GL_RGB4_S3TC 0x83A1 #endif #ifndef GL_RGBA_S3TC #define GL_RGBA_S3TC 0x83A2 #endif #ifndef GL_RGBA4_S3TC #define GL_RGBA4_S3TC 0x83A3 #endif static constexpr size_t GL_FRAME_VERTEX_CAPACITY = 2000000; // we make retro games, this comes out to about 100mb or so. struct QD3D12Window; struct QD3D12GLContext; struct QD3D12Pbuffer; static HDC g_qd3d12CurrentDC = nullptr; static HGLRC g_qd3d12CurrentRC = nullptr; static D3D12_GPU_DESCRIPTOR_HANDLE QD3D12_SrvGpu(UINT index); static D3D12_CPU_DESCRIPTOR_HANDLE QD3D12_SrvCpu(UINT index); static Mat4 CurrentModelMatrix(); static inline GLenum QD3D12_MapCompatTextureTarget(GLenum target); // New optional tangent-space normal-map API entry points. These are declared // here as well as in opengl.h so qd3d12_wglGetProcAddress can reference them // even when this cpp is built before the public header is refreshed. void APIENTRY glTangent3f(GLfloat x, GLfloat y, GLfloat z); void APIENTRY glTangent3fv(const GLfloat* v); void APIENTRY glBinormal3f(GLfloat x, GLfloat y, GLfloat z); void APIENTRY glBinormal3fv(const GLfloat* v); void APIENTRY glTagTextureNormalMap(GLuint texture, GLboolean isNormalMap); void APIENTRY glTextureNormalMap(GLuint texture, GLboolean isNormalMap); void APIENTRY glBindNormalMapTexture(GLuint texture); void APIENTRY glNormalMapTexture(GLuint texture); void APIENTRY glNormalMapStrengthf(GLfloat strength); void APIENTRY glNormalMapYSignf(GLfloat sign); void APIENTRY glNeuralPOMMaterialQD3D12(GLuint texture, GLsizei weightsBytes, const GLvoid* weightsData, GLsizei latentBytes, const GLvoid* latentRGBA16FData); void APIENTRY glNeuralPOMMaterialFromBoundTextureQD3D12(GLsizei weightsBytes, const GLvoid* weightsData, GLsizei latentBytes, const GLvoid* latentRGBA16FData); void APIENTRY glClearNeuralPOMMaterialQD3D12(GLuint texture); void APIENTRY glBindNeuralPOMTextureQD3D12(GLuint texture); void APIENTRY glNeuralPOMTextureQD3D12(GLuint texture); void APIENTRY glEnableNeuralPOMQD3D12(GLboolean enable); 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 glTagTextureSpecularMap(GLuint texture, GLboolean isSpecularMap); void APIENTRY glTextureSpecularMap(GLuint texture, GLboolean isSpecularMap); void APIENTRY glBindSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapStrengthf(GLfloat strength); void APIENTRY glRaytracingMaterialFlagsQD3D12(GLuint flags); void APIENTRY glRaytracingMaterialFlagQD3D12(GLuint flag, GLboolean enable); // DXR lighting controls are defined in gl_raytracing.cpp. They are declared // here too so glLightScene() can force the ray pass to emit raw/noisy radiance // whenever an external denoiser such as DLSS Ray Reconstruction will consume it. 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 glRaytracingLightingSetSpecularInput(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); // Fast TLAS instance visibility controls are implemented in gl_raytracing.cpp. // They flip the D3D12_RAYTRACING_INSTANCE_DESC InstanceMask via the existing // TLAS update path, so hiding/showing a model does not rebuild or recreate BLAS. int glRaytracingSetInstanceVisibilityInScene(glRaytracingSceneHandle_t sceneHandle, glRaytracingInstanceHandle_t instanceHandle, int visible); int glRaytracingGetInstanceVisibilityInScene(glRaytracingSceneHandle_t sceneHandle, glRaytracingInstanceHandle_t instanceHandle); void glRaytracingHideAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle); void glRaytracingShowAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle); void glRaytracingHideAllInstances(void); void glRaytracingShowAllInstances(void); // Shim/public helpers for the app's top-level acceleration-structure handles. int glSetTopLevelAccelStructureVisible(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle, int visible); void glHideTopLevelAccelStructure(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); void glShowTopLevelAccelStructure(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); int glIsTopLevelAccelStructureVisible(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); void glHideAllTopLevelAccelStructures(glRaytracingSceneHandle_t scene); void glShowAllTopLevelAccelStructures(glRaytracingSceneHandle_t scene); // Backward-compatible aliases matching the existing Aceel typo in this file. int glSetTopLevelAceelStructureVisible(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle, int visible); void glHideTopLevelAceelStructure(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); void glShowTopLevelAceelStructure(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); int glIsTopLevelAceelStructureVisible(glRaytracingSceneHandle_t scene, uint32_t topLevelHandle); void glHideAllTopLevelAceelStructures(glRaytracingSceneHandle_t scene); void glShowAllTopLevelAceelStructures(glRaytracingSceneHandle_t scene); void QD3D12_SetUpscalerBackend(int backend); void QD3D12_SetUpscalerQuality(int quality); void QD3D12_SetUpscalerSharpness(float sharpness); void QD3D12_SetToneMapBrightness(float brightness); float QD3D12_GetToneMapBrightness(void); void APIENTRY glToneMapBrightnessQD3D12(GLfloat brightness); void APIENTRY glToneMapBrightnessfQD3D12(GLfloat brightness); void APIENTRY glTonemapBrightnessQD3D12(GLfloat brightness); void APIENTRY glTonemapBrightnessfQD3D12(GLfloat brightness); void QD3D12_EnableRayAIDenoise(int enabled); void QD3D12_EnableDLSSRayReconstruction(int enabled); void QD3D12_EnableFSRRayRegeneration(int enabled); void QD3D12_EnableDLAA(int enabled); int QD3D12_IsDLAAEnabled(void); void APIENTRY glDLAAQD3D12(GLboolean enable); void APIENTRY glEnableDLAAQD3D12(GLboolean enable); void QD3D12_EnableTAA(int enabled); int QD3D12_IsTAAEnabled(void); void APIENTRY glTAAQD3D12(GLboolean enable); void APIENTRY glEnableTAAQD3D12(GLboolean enable); void APIENTRY glTemporalAAQD3D12(GLboolean enable); void APIENTRY glEnableTemporalAAQD3D12(GLboolean enable); void QD3D12_SetPathTracingQuality(uint32_t samplesPerPixel, uint32_t maxBounces); void QD3D12_SetPathTracingFallbackSamples(uint32_t samplesPerPixel); void QD3D12_SetCameraInfo( const float* viewToClip, const float* clipToView, const float* clipToPrevClip, const float* prevClipToClip, const float* cameraPos, const float* cameraRight, const float* cameraUp, const float* cameraForward, float nearPlane, float farPlane, float verticalFovRadians, float aspectRatio); static void QD3D12_CreateUploadRingForWindow(struct QD3D12Window& w); static void QD3D12_DestroyUploadRingForWindow(struct QD3D12Window& w); static void QD3D12_SubmitOpenFrameNoPresentAndWait(); static void QD3D12_EnsureFrameOpen(); static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w); static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w); static bool QD3D12_RunInternalTAA(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_GPU_DESCRIPTOR_HANDLE& outputSrv); static bool QD3D12_FinalBlitToBackBuffer(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, const D3D12_VIEWPORT& outputViewport, const D3D12_RECT& outputScissor, bool toneMapInput = false); static void QD3D12_RunRayAIDenoiseIfEnabled(ID3D12GraphicsCommandList* cl, QD3D12Window& w, ID3D12Resource* lightingResource); static void QD3D12_TransitionResource(ID3D12GraphicsCommandList* cl, ID3D12Resource* res, D3D12_RESOURCE_STATES& trackedState, D3D12_RESOURCE_STATES newState); static UINT QD3D12_GBufferSampleCount(); static bool QD3D12_GBufferMsaaEnabled(); static UINT QD3D12_ActiveRasterWidth(const QD3D12Window& w); static UINT QD3D12_ActiveRasterHeight(const QD3D12Window& w); static void QD3D12_BindTargetsForCurrentPhase(QD3D12Window& w); static void QD3D12_ResolveGBufferForCurrentFrame(QD3D12Window& w); void APIENTRY QD3D12_ResolveGBufferNow(void); void APIENTRY glResolveGBufferQD3D12(void); static void QD3D12_ResolveSceneToOutputAndEnterNativePhase(QD3D12Window& w); static void QD3D12_RegisterWindowDC(QD3D12Window* w); static void QD3D12_UnregisterWindowDC(HDC dc); static HGLRC QD3D12_CreateGLContextHandle(HDC dc, QD3D12Window* window); extern bool D3D12_SwapBufferMultWindows; static void QD3D12_Log(const char* fmt, ...) { char buffer[4096]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); OutputDebugStringA(buffer); OutputDebugStringA("\n"); } static void QD3D12_Fatal(const char* fmt, ...) { char buffer[4096]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); OutputDebugStringA(buffer); OutputDebugStringA("\n"); MessageBoxA(nullptr, buffer, "QD3D12 Fatal", MB_OK | MB_ICONERROR); DebugBreak(); } struct VertexArena { GLVertex* buffer; size_t capacity; size_t cursor; }; #define QD3D12_CHECK(x) do { HRESULT _hr = (x); if (FAILED(_hr)) { QD3D12_Fatal("HRESULT failed 0x%08X at %s:%d", (unsigned)_hr, __FILE__, __LINE__); } } while(0) struct GLOcclusionQuery { GLuint id = 0; bool active = false; bool pending = false; bool resultReady = false; UINT heapIndex = UINT_MAX; UINT64 result = 0; UINT64 submittedFence = 0; }; struct QueryMarker { enum Type { Begin, End }; Type type = Begin; GLuint id = 0; }; static const UINT QD3D12_MaxQueries = 2048; // ============================================================ // SECTION 3: D3D12 renderer structs // ============================================================ static const UINT QD3D12_MaxTextureUnits = 8; static const UINT QD3D12_FrameCount = 2; static const UINT QD3D12_MaxTextures = 4096; static const UINT QD3D12_UploadBufferSize = 128 * 1024 * 1024; // MSAA has been removed from the G-buffer path. Temporal AA now owns edge cleanup // after lighting, so all scene/deferred render targets remain single-sample. static const UINT QD3D12_RequestedGBufferSampleCount = 1; static constexpr UINT QD3D12_PostSrvCount = 11; static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount; static constexpr UINT QD3D12_PostConstantDwords = 24; static constexpr UINT QD3D12_ToneMapTileDim = 64; enum QD3D12RTVSlotGroup { QD3D12_RTV_SCENE_RENDER = 0, QD3D12_RTV_NORMAL_RENDER = 1, QD3D12_RTV_POSITION_RENDER = 2, QD3D12_RTV_VELOCITY_RENDER = 3, 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_SPECULAR_RENDER = 11, QD3D12_RTV_SPECULAR_RESOLVED = 12, QD3D12_RTV_TAA_OUTPUT = 13, QD3D12_RTV_TONEMAP_TILE_MAX = 14, QD3D12_RTV_TONEMAP_SCENE_MAX = 15, QD3D12_RTV_GROUP_COUNT = 16 }; 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_SpecularAlbedoFormat = DXGI_FORMAT_R8G8B8A8_UNORM; 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; static const DXGI_FORMAT QD3D12_DepthResourceFormat = DXGI_FORMAT_R32G8X24_TYPELESS; static const DXGI_FORMAT QD3D12_DepthSrvFormat = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; static const DXGI_FORMAT QD3D12_DepthDsvFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT; enum QD3D12UpscalerBackend { QD3D12_UPSCALER_NONE = 0, QD3D12_UPSCALER_DLSS = 1, QD3D12_UPSCALER_FSR = 2 }; enum QD3D12UpscalerQuality { QD3D12_QUALITY_NATIVE = 0, QD3D12_QUALITY_QUALITY, QD3D12_QUALITY_BALANCED, QD3D12_QUALITY_PERFORMANCE, QD3D12_QUALITY_ULTRA_PERFORMANCE, QD3D12_QUALITY_DLAA }; enum QD3D12FramePhase { QD3D12_FRAME_LOW_RES = 0, QD3D12_FRAME_NATIVE_POST_UPSCALE }; struct QD3D12CameraState { Mat4 viewToClip = Mat4::Identity(); Mat4 clipToView = Mat4::Identity(); Mat4 clipToPrevClip = Mat4::Identity(); Mat4 prevClipToClip = Mat4::Identity(); float cameraPos[3] = { 0.0f, 0.0f, 0.0f }; float cameraRight[3] = { 1.0f, 0.0f, 0.0f }; float cameraUp[3] = { 0.0f, 1.0f, 0.0f }; float cameraForward[3] = { 0.0f, 0.0f, 1.0f }; float nearPlane = 0.01f; float farPlane = 4096.0f; float verticalFovRadians = 1.0471975512f; float aspectRatio = 1.0f; bool valid = false; }; enum PipelineMode { PIPE_OPAQUE_TEX = 0, PIPE_ALPHA_TEST_TEX, PIPE_BLEND_TEX, PIPE_OPAQUE_UNTEX, PIPE_BLEND_UNTEX, PIPE_COUNT }; static void QD3D12_FlushQueuedBatches(); static PipelineMode PickPipeline(bool useTex0, bool useTex1); static Mat4 CurrentMVP(); struct RetiredResource { UINT64 fenceValue = 0; ComPtr resource; }; static std::vector g_retiredResources; struct FrameResources { ComPtr cmdAlloc; UINT64 fenceValue = 0; }; struct UploadRing { ComPtr resource[QD3D12_FrameCount]; uint8_t* cpuBase[QD3D12_FrameCount] = {}; D3D12_GPU_VIRTUAL_ADDRESS gpuBase[QD3D12_FrameCount] = {}; UINT size = 0; UINT offset = 0; }; // GPU-only Neural POM payload. The CPU never evaluates or samples this data; // it only stores uploaded bytes and copies them into raw D3D12 buffers. The // fixed-function HLSL reads the weights header, weights, and latent grid through // ByteAddressBuffer SRVs at draw time. struct NeuralPOMResource { bool hasData = false; bool gpuValid = false; uint64_t generation = 0; uint64_t gpuGeneration = 0; std::vector weightsBytes; std::vector latentRGBA16FBytes; ComPtr weightsBuffer; ComPtr latentBuffer; D3D12_RESOURCE_STATES weightsState = D3D12_RESOURCE_STATE_COMMON; D3D12_RESOURCE_STATES latentState = D3D12_RESOURCE_STATE_COMMON; D3D12_CPU_DESCRIPTOR_HANDLE weightsSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE weightsSrvGpu{}; UINT weightsSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE latentSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE latentSrvGpu{}; UINT latentSrvIndex = UINT_MAX; }; struct TextureResource { GLuint glId = 0; int width = 0; int height = 0; GLenum format = GL_RGBA; GLenum minFilter = GL_LINEAR; GLenum magFilter = GL_LINEAR; GLenum wrapS = GL_REPEAT; GLenum wrapT = GL_REPEAT; std::vector sysmem; bool compressed = false; GLenum compressedInternalFormat = 0; UINT compressedBlockBytes = 0; UINT compressedImageSize = 0; bool forceOpaqueAlpha = false; DXGI_FORMAT dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; UINT mipLevels = 1; // Async mip generation state. Mip 0 is uploaded immediately; the CPU-built // lower mips are produced by worker threads and copied into the D3D12 texture // at the end of a later frame. uint64_t cpuGeneration = 0; uint64_t mipJobQueuedGeneration = 0; uint64_t mipChainResidentGeneration = 0; uint64_t pendingMipGeneration = 0; bool mipChainRequested = false; bool mipChainResident = false; std::vector> pendingMipChain; ComPtr texture; D3D12_CPU_DESCRIPTOR_HANDLE srvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE srvGpu{}; UINT srvIndex = UINT_MAX; bool gpuValid = false; D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST; // 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; bool isSpecularMap = false; // Optional trained neural replacement for shader POM, usually attached to the // normal-map texture. If this is absent or not resident, the old path runs. NeuralPOMResource neuralPOM; }; static void QD3D12_ShutdownMipWorkers(); static void QD3D12_ProcessCompletedTextureMipJobs(UINT maxUploads = 2); static void QD3D12_RequestAsyncMipBuild(TextureResource& tex); static void QD3D12_InvalidateTextureMipChain(TextureResource& tex, bool invalidateBase); static bool QD3D12_TextureHasNeuralPOMData(const TextureResource& tex); static bool QD3D12_UploadNeuralPOM(TextureResource& tex); static UINT QD3D12_NeuralPOMFallbackSrvIndex(); static void QD3D12_CreateNeuralPOMZeroBuffer(); struct DrawConstants { Mat4 mvp; Mat4 prevMvp; Mat4 modelMatrix; float alphaRef; float useTex0; float useTex1; float tex1IsLightmap; float texEnvMode0; float texEnvMode1; float geometryFlag; float roughness; float fogEnabled; float fogMode; float fogDensity; float fogStart; float fogEnd; float PointSize; float materialType; float alphaFunc; float fogColor[4]; float renderSize[2]; float invRenderSize[2]; float jitterPixels[2]; float prevJitterPixels[2]; float _motionPad[4]; float materialMapPad[4]; float texComb0RGB[4]; float texComb0Alpha[4]; float texComb0Operand[4]; float texEnvColor0[4]; float texComb1RGB[4]; float texComb1Alpha[4]; float texComb1Operand[4]; float texEnvColor1[4]; float cameraPomPad[4]; float neuralPomPad[4]; // Appended for translated ARBvp/ARBfp programs. Fixed-function HLSL ignores // these fields; ARB-generated HLSL uses them as program.env/local storage. float arbEnv[QD3D12_ARB_MAX_PROGRAM_PARAMETERS][4]; float arbLocalVP[QD3D12_ARB_MAX_PROGRAM_PARAMETERS][4]; float arbLocalFP[QD3D12_ARB_MAX_PROGRAM_PARAMETERS][4]; }; struct GLBufferObject { GLuint id = 0; GLenum target = 0; GLbitfield storageFlags = 0; std::vector data; bool mapped = false; GLintptr mappedOffset = 0; GLsizeiptr mappedLength = 0; GLbitfield mappedAccess = 0; }; 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_glow_map GL_QD3D12_specular_map GL_QD3D12_glass_material GL_QD3D12_volumetric_light GL_QD3D12_dlaa GL_QD3D12_taa GL_QD3D12_tonemap_brightness GL_QD3D12_neural_pom"; enum TexEnvModeShader { TEXENV_MODULATE = 0, TEXENV_REPLACE = 1, TEXENV_DECAL = 2, TEXENV_BLEND = 3, TEXENV_ADD = 4, TEXENV_COMBINE = 5 }; static float MapTexEnvMode(GLenum mode) { switch (mode) { case GL_REPLACE: return (float)TEXENV_REPLACE; case GL_BLEND: return (float)TEXENV_BLEND; #ifdef GL_COMBINE_ARB case GL_COMBINE_ARB: return (float)TEXENV_COMBINE; #endif #ifdef GL_ADD case GL_ADD: return (float)TEXENV_ADD; #endif case GL_MODULATE: default: return (float)TEXENV_MODULATE; } } struct BatchKey { D3D12_VIEWPORT viewport; D3D12_RECT scissor; PipelineMode pipeline = PIPE_OPAQUE_UNTEX; D3D12_PRIMITIVE_TOPOLOGY topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; bool useTessellation = false; UINT tex0SrvIndex = 0; UINT tex1SrvIndex = 0; UINT textureSrvIndex[QD3D12_MaxTextureUnits] = {}; UINT normalMapSrvIndex = 0; float useNormalMap = 0.0f; float normalMapStrength = 1.0f; float normalMapYSign = 1.0f; UINT glowMapSrvIndex = 0; float useGlowMap = 0.0f; float glowMapStrength = 1.0f; UINT specularMapSrvIndex = 0; float useSpecularMap = 0.0f; float specularMapStrength = 1.0f; UINT neuralPOMWeightsSrvIndex = UINT_MAX; UINT neuralPOMLatentSrvIndex = UINT_MAX; float useNeuralPOM = 0.0f; bool useARBPrograms = false; GLuint arbVertexProgram = 0; GLuint arbFragmentProgram = 0; uint32_t arbVertexRevision = 0; uint32_t arbFragmentRevision = 0; ID3DBlob* arbVertexBlob = nullptr; ID3DBlob* arbFragmentBlob = nullptr; QD3D12ARBDrawConstantArrays arbConstants{}; float texComb0RGB[4] = {}; float texComb0Alpha[4] = {}; float texComb0Operand[4] = {}; float texEnvColor0[4] = {}; float texComb1RGB[4] = {}; float texComb1Alpha[4] = {}; float texComb1Operand[4] = {}; float texEnvColor1[4] = {}; UINT8 colorWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; bool cullFaceEnabled = false; GLenum cullMode = GL_BACK; GLenum frontFace = GL_CCW; bool stencilTest = false; UINT8 stencilReadMask = 0xFF; UINT8 stencilWriteMask = 0xFF; UINT8 stencilRef = 0; GLenum stencilFrontFunc = GL_ALWAYS; GLenum stencilFrontSFail = GL_KEEP; GLenum stencilFrontDPFail = GL_KEEP; GLenum stencilFrontDPPass = GL_KEEP; GLenum stencilBackFunc = GL_ALWAYS; GLenum stencilBackSFail = GL_KEEP; GLenum stencilBackDPFail = GL_KEEP; GLenum stencilBackDPPass = GL_KEEP; bool depthBoundsTest = false; float depthBoundsMin = 0.0f; float depthBoundsMax = 1.0f; bool polygonOffsetEnabled = false; float polygonOffsetFactor = 0.0f; float polygonOffsetUnits = 0.0f; float alphaRef = 0.0f; float alphaFunc = 4.0f; float useTex0 = 0.0f; float useTex1 = 0.0f; float tex1IsLightmap = 0.0f; float texEnvMode0 = 0.0f; float texEnvMode1 = 0.0f; float fogEnabled = 0.0f; float fogMode = 1.0f; float fogDensity = 1.0f; float fogStart = 0.0f; float fogEnd = 1.0f; float fogColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; GLenum blendSrc = GL_ONE; GLenum blendDst = GL_ZERO; bool depthTest = true; bool depthWrite = true; GLenum depthFunc = GL_LEQUAL; Mat4 mvp; Mat4 prevMvp; Mat4 modelMatrix; float geometryFlag = 0.0f; float roughness = 0.5f; float materialType = 0.0f; GLuint motionObjectId = 0; float cameraWorldPos[3] = { 0.0f, 0.0f, 0.0f }; float cameraValid = 0.0f; }; static bool ViewportEquals(const D3D12_VIEWPORT& a, const D3D12_VIEWPORT& b) { return a.TopLeftX == b.TopLeftX && a.TopLeftY == b.TopLeftY && a.Width == b.Width && a.Height == b.Height && a.MinDepth == b.MinDepth && a.MaxDepth == b.MaxDepth; } static bool RectEquals(const D3D12_RECT& a, const D3D12_RECT& b) { return a.left == b.left && a.top == b.top && a.right == b.right && a.bottom == b.bottom; } static bool TextureSrvArrayEquals(const UINT* a, const UINT* b) { for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { if (a[i] != b[i]) return false; } return true; } static bool BatchKeyEquals(const BatchKey& a, const BatchKey& b) { return ViewportEquals(a.viewport, b.viewport) && RectEquals(a.scissor, b.scissor) && a.pipeline == b.pipeline && a.topology == b.topology && a.useTessellation == b.useTessellation && a.tex0SrvIndex == b.tex0SrvIndex && a.tex1SrvIndex == b.tex1SrvIndex && a.normalMapSrvIndex == b.normalMapSrvIndex && 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.specularMapSrvIndex == b.specularMapSrvIndex && a.useSpecularMap == b.useSpecularMap && a.specularMapStrength == b.specularMapStrength && a.neuralPOMWeightsSrvIndex == b.neuralPOMWeightsSrvIndex && a.neuralPOMLatentSrvIndex == b.neuralPOMLatentSrvIndex && a.useNeuralPOM == b.useNeuralPOM && a.alphaRef == b.alphaRef && a.alphaFunc == b.alphaFunc && a.useTex0 == b.useTex0 && a.useTex1 == b.useTex1 && a.tex1IsLightmap == b.tex1IsLightmap && a.texEnvMode0 == b.texEnvMode0 && a.texEnvMode1 == b.texEnvMode1 && a.fogEnabled == b.fogEnabled && a.fogMode == b.fogMode && a.fogDensity == b.fogDensity && a.fogStart == b.fogStart && a.fogEnd == b.fogEnd && memcmp(a.fogColor, b.fogColor, sizeof(a.fogColor)) == 0 && a.colorWriteMask == b.colorWriteMask && a.cullFaceEnabled == b.cullFaceEnabled && a.cullMode == b.cullMode && a.frontFace == b.frontFace && a.stencilTest == b.stencilTest && a.stencilReadMask == b.stencilReadMask && a.stencilWriteMask == b.stencilWriteMask && a.stencilRef == b.stencilRef && a.stencilFrontFunc == b.stencilFrontFunc && a.stencilFrontSFail == b.stencilFrontSFail && a.stencilFrontDPFail == b.stencilFrontDPFail && a.stencilFrontDPPass == b.stencilFrontDPPass && a.stencilBackFunc == b.stencilBackFunc && a.stencilBackSFail == b.stencilBackSFail && a.stencilBackDPFail == b.stencilBackDPFail && a.stencilBackDPPass == b.stencilBackDPPass && a.depthBoundsTest == b.depthBoundsTest && a.depthBoundsMin == b.depthBoundsMin && a.depthBoundsMax == b.depthBoundsMax && a.polygonOffsetEnabled == b.polygonOffsetEnabled && a.polygonOffsetFactor == b.polygonOffsetFactor && a.polygonOffsetUnits == b.polygonOffsetUnits && memcmp(a.texComb0RGB, b.texComb0RGB, sizeof(a.texComb0RGB)) == 0 && memcmp(a.texComb0Alpha, b.texComb0Alpha, sizeof(a.texComb0Alpha)) == 0 && memcmp(a.texComb0Operand, b.texComb0Operand, sizeof(a.texComb0Operand)) == 0 && memcmp(a.texEnvColor0, b.texEnvColor0, sizeof(a.texEnvColor0)) == 0 && memcmp(a.texComb1RGB, b.texComb1RGB, sizeof(a.texComb1RGB)) == 0 && memcmp(a.texComb1Alpha, b.texComb1Alpha, sizeof(a.texComb1Alpha)) == 0 && memcmp(a.texComb1Operand, b.texComb1Operand, sizeof(a.texComb1Operand)) == 0 && memcmp(a.texEnvColor1, b.texEnvColor1, sizeof(a.texEnvColor1)) == 0 && a.blendSrc == b.blendSrc && a.blendDst == b.blendDst && a.depthTest == b.depthTest && a.depthWrite == b.depthWrite && a.depthFunc == b.depthFunc && a.geometryFlag == b.geometryFlag && a.roughness == b.roughness && a.materialType == b.materialType && a.motionObjectId == b.motionObjectId && a.cameraValid == b.cameraValid && memcmp(a.cameraWorldPos, b.cameraWorldPos, sizeof(a.cameraWorldPos)) == 0 && TextureSrvArrayEquals(a.textureSrvIndex, b.textureSrvIndex) && a.useARBPrograms == b.useARBPrograms && a.arbVertexProgram == b.arbVertexProgram && a.arbFragmentProgram == b.arbFragmentProgram && a.arbVertexRevision == b.arbVertexRevision && a.arbFragmentRevision == b.arbFragmentRevision && a.arbVertexBlob == b.arbVertexBlob && a.arbFragmentBlob == b.arbFragmentBlob && (!a.useARBPrograms || memcmp(&a.arbConstants, &b.arbConstants, sizeof(a.arbConstants)) == 0) && memcmp(a.mvp.m, b.mvp.m, sizeof(a.mvp.m)) == 0 && memcmp(a.prevMvp.m, b.prevMvp.m, sizeof(a.prevMvp.m)) == 0 && memcmp(a.modelMatrix.m, b.modelMatrix.m, sizeof(a.modelMatrix.m)) == 0; } struct QueuedBatch { BatchKey key; size_t markerBegin = 0; size_t markerEnd = 0; size_t firstVertex; size_t vertexCount; }; struct QD3D12Window { HWND hwnd = nullptr; HDC hdc = nullptr; bool ownsHdc = false; bool isPbuffer = false; UINT width = 640; // display/output size UINT height = 480; UINT renderWidth = 640; // internal render size (may be lower for upscalers) UINT renderHeight = 480; ComPtr swapChain; UINT frameIndex = 0; ComPtr rtvHeap; UINT rtvStride = 0; // RTV descriptor groups: // SCENE_RENDER/normal/position/velocity : active low-res render targets // (MSAA textures when enabled, resolved single-sample textures otherwise) // *_RESOLVED : single-sample outputs used after MSAA conversion. // Scene color uses hardware resolve; normals/position/velocity are point-resolved. // BACKBUFFER : swap-chain/output render target std::array, QD3D12_FrameCount> sceneColorBuffers; D3D12_RESOURCE_STATES sceneColorState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> normalBuffers; D3D12_RESOURCE_STATES normalBufferState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> positionBuffers; D3D12_RESOURCE_STATES positionBufferState[QD3D12_FrameCount] = {}; 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> specularBuffers; D3D12_RESOURCE_STATES specularBufferState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> sceneColorMsaaBuffers; D3D12_RESOURCE_STATES sceneColorMsaaState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> normalMsaaBuffers; D3D12_RESOURCE_STATES normalMsaaState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> positionMsaaBuffers; D3D12_RESOURCE_STATES positionMsaaState[QD3D12_FrameCount] = {}; 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> specularMsaaBuffers; D3D12_RESOURCE_STATES specularMsaaState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> backBuffers; D3D12_RESOURCE_STATES backBufferState[QD3D12_FrameCount] = {}; // Streamline/DLSS/DLSS-RR writes through UAV, so do not tag the swap-chain // backbuffer as kBufferTypeScalingOutputColor. Streamline writes here first; // the shim then samples this texture and copies into the swap-chain RTV. std::array, QD3D12_FrameCount> slOutputBuffers; D3D12_RESOURCE_STATES slOutputState[QD3D12_FrameCount] = {}; // Internal TAA history/output. This pass runs after lighting and before the // native 2D phase, then the final sharpen pass samples this buffer. std::array, QD3D12_FrameCount> taaBuffers; D3D12_RESOURCE_STATES taaBufferState[QD3D12_FrameCount] = {}; bool taaHistoryValid[QD3D12_FrameCount] = {}; // Tone mapping uses two tiny FP16 reduction targets: 64x64 tile maxima, then // a 1x1 scene max. The tone-map pass treats that 1x1 value as the white point. std::array, QD3D12_FrameCount> toneMapTileMaxBuffers; D3D12_RESOURCE_STATES toneMapTileMaxState[QD3D12_FrameCount] = {}; std::array, QD3D12_FrameCount> toneMapSceneMaxBuffers; D3D12_RESOURCE_STATES toneMapSceneMaxState[QD3D12_FrameCount] = {}; ComPtr dsvHeap; ComPtr depthBuffer; D3D12_RESOURCE_STATES depthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; ComPtr depthMsaaBuffer; D3D12_RESOURCE_STATES depthMsaaState = D3D12_RESOURCE_STATE_DEPTH_WRITE; ComPtr nativeDepthBuffer; D3D12_RESOURCE_STATES nativeDepthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; UINT sceneColorSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE sceneColorSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE sceneColorSrvGpu[QD3D12_FrameCount]{}; UINT normalSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE normalSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE normalSrvGpu[QD3D12_FrameCount]{}; UINT positionSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE positionSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE positionSrvGpu[QD3D12_FrameCount]{}; UINT velocitySrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; 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 specularSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE specularSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE specularSrvGpu[QD3D12_FrameCount]{}; UINT slOutputSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE slOutputSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE slOutputSrvGpu[QD3D12_FrameCount]{}; UINT taaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE taaSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE taaSrvGpu[QD3D12_FrameCount]{}; UINT toneMapTileMaxSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE toneMapTileMaxSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE toneMapTileMaxSrvGpu[QD3D12_FrameCount]{}; UINT toneMapSceneMaxSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE toneMapSceneMaxSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE toneMapSceneMaxSrvGpu[QD3D12_FrameCount]{}; UINT depthSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE depthSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE depthSrvGpu{}; UINT depthMsaaSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE depthMsaaSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE depthMsaaSrvGpu{}; UINT normalMsaaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE normalMsaaSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE normalMsaaSrvGpu[QD3D12_FrameCount]{}; UINT positionMsaaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE positionMsaaSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE positionMsaaSrvGpu[QD3D12_FrameCount]{}; UINT velocityMsaaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; 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]{}; UINT specularMsaaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; D3D12_CPU_DESCRIPTOR_HANDLE specularMsaaSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE specularMsaaSrvGpu[QD3D12_FrameCount]{}; D3D12_VIEWPORT viewport{}; D3D12_RECT scissor{}; UploadRing upload; FrameResources frames[QD3D12_FrameCount]; }; 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; GLVertex* ptr = nullptr; size_t count = 0; void Init(size_t initialCapacity = 1024) { storage.resize(initialCapacity); ptr = storage.data(); count = 0; } void Clear() { count = 0; } GLVertex& Push() { if (count >= storage.size()) { GLVertex* oldPtr = ptr; const size_t newSize = storage.empty() ? 1024 : storage.size() * 2; storage.resize(newSize); GLVertex* newPtr = storage.data(); if (newPtr != oldPtr) { ptr = newPtr; } else { ptr = newPtr; } } return ptr[count++]; } __forceinline GLVertex* Data() { return ptr; } const GLVertex* Data() const { return ptr; } size_t Size() const { return count; } }; struct GLState { VertexArena frameVerts; GLfloat pointSize = 1.0f; GLfloat pointSizeMin = 1.0f; GLfloat pointSizeMax = 64.0f; GLfloat pointFadeThresholdSize = 1.0f; GLfloat pointDistanceAttenuation[3] = { 1.0f, 0.0f, 0.0f }; std::unordered_map queries; GLuint nextQueryId = 1; GLuint currentQuery = 0; std::vector queryMarkers; ComPtr occlusionQueryHeap; ComPtr occlusionReadback; uint64_t* occlusionReadbackCpu = nullptr; std::unordered_map buffers; GLuint nextBufferId = 1; GLuint boundArrayBuffer = 0; GLuint boundElementArrayBuffer = 0; Mat4 modelMatrix = Mat4::Identity(); float currentGeometryFlag = 0.0f; bool fog = false; GLenum fogMode = GL_EXP; GLfloat fogDensity = 1.0f; GLfloat fogStart = 0.0f; GLfloat fogEnd = 1.0f; GLfloat fogColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; GLenum fogHint = GL_DONT_CARE; GLclampd depthRangeNear = 0.0; GLclampd depthRangeFar = 1.0; std::vector queuedBatches; bool frameOpen = false; QD3D12Window* frameOwner = nullptr; QD3D12FramePhase framePhase = QD3D12_FRAME_LOW_RES; bool sceneResolvedThisFrame = false; bool gbufferResolvedThisFrame = false; bool raytracedLightingReadyThisFrame = false; bool sceneFogValidThisFrame = false; float sceneFogMode = 1.0f; float sceneFogDensity = 1.0f; float sceneFogStart = 0.0f; float sceneFogEnd = 1.0f; float sceneFogColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; float sceneFogCameraWorldPos[3] = { 0.0f, 0.0f, 0.0f }; float sceneFogCameraValid = 0.0f; float upscalerSharpness = 1.0f; float toneMapBrightness = 1.0f; GLenum depthFunc = GL_LEQUAL; UINT64 nextFenceValue = 1; GLuint currentMotionObjectId = 0; float currentSurfaceRoughness = 0.5f; float currentMaterialType = 0.0f; uint32_t currentRayMaterialFlags = 0; std::unordered_map prevObjectMVPs; std::unordered_map currObjectMVPs; uint64_t frameSerial = 0; bool motionHistoryReset = true; QD3D12UpscalerBackend upscalerBackend = QD3D12_UPSCALER_DLSS; QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_PERFORMANCE; bool enableInternalTAA = true; bool enableRayAIDenoise = false; bool enableDLSSRayReconstruction = true; bool enableFSRRayRegeneration = false; uint32_t pathTracingSamplesPerPixel = 1; uint32_t pathTracingFallbackSamplesPerPixel = 2; uint32_t pathTracingMaxBounces = 2; float jitterX = 0.0f; float jitterY = 0.0f; float prevJitterX = 0.0f; float prevJitterY = 0.0f; QD3D12CameraState cameraState{}; struct ClientArrayState { GLint size = 4; GLenum type = GL_FLOAT; GLsizei stride = 0; const uint8_t* ptr = nullptr; bool enabled = false; }; ClientArrayState vertexArray; ClientArrayState normalArray; ClientArrayState tangentArray; ClientArrayState bitangentArray; ClientArrayState colorArray; ClientArrayState texCoordArray[QD3D12_MaxTextureUnits]; GLuint clientActiveTextureUnit = 0; bool scissorTest = false; GLint scissorX = 0; GLint scissorY = 0; GLsizei scissorW = 640; GLsizei scissorH = 480; GLenum lastError = GL_NO_ERROR; GLuint stencilMask = ~0u; GLint clearStencilValue = 0; bool stencilTest = false; bool stencilTwoSide = false; GLenum activeStencilFace = GL_FRONT; GLenum stencilFunc = GL_ALWAYS; GLint stencilRef = 0; GLuint stencilFuncMask = ~0u; GLenum stencilSFail = GL_KEEP; GLenum stencilDPFail = GL_KEEP; GLenum stencilDPPass = GL_KEEP; GLuint stencilFrontMask = ~0u; GLenum stencilFrontFunc = GL_ALWAYS; GLint stencilFrontRef = 0; GLuint stencilFrontFuncMask = ~0u; GLenum stencilFrontSFail = GL_KEEP; GLenum stencilFrontDPFail = GL_KEEP; GLenum stencilFrontDPPass = GL_KEEP; GLuint stencilBackMask = ~0u; GLenum stencilBackFunc = GL_ALWAYS; GLint stencilBackRef = 0; GLuint stencilBackFuncMask = ~0u; GLenum stencilBackSFail = GL_KEEP; GLenum stencilBackDPFail = GL_KEEP; GLenum stencilBackDPPass = GL_KEEP; bool depthBoundsTest = false; GLclampd depthBoundsMin = 0.0; GLclampd depthBoundsMax = 1.0; GLdouble clipPlane0[4] = { 0.0, 0.0, 0.0, 0.0 }; bool clipPlane0Enabled = false; GLfloat polygonOffsetFactor = 0.0f; GLfloat polygonOffsetUnits = 0.0f; bool polygonOffsetPoint = false; bool polygonOffsetLine = false; bool polygonOffsetFill = false; GLclampd clearDepthValue = 1.0; GLboolean colorMaskR = GL_TRUE; GLboolean colorMaskG = GL_TRUE; GLboolean colorMaskB = GL_TRUE; GLboolean colorMaskA = GL_TRUE; float clearColor[4] = { 0, 0, 0, 1 }; bool blend = false; bool alphaTest = false; bool depthTest = true; bool cullFace = false; bool texture2D[QD3D12_MaxTextureUnits] = { true, false }; bool depthWrite = true; GLenum blendSrc = GL_SRC_ALPHA; GLenum blendDst = GL_ONE_MINUS_SRC_ALPHA; GLenum alphaFunc = GL_GREATER; float alphaRef = 0.666f; GLenum cullMode = GL_BACK; GLenum frontFace = GL_CCW; GLenum shadeModel = GL_FLAT; GLenum drawBuffer = GL_BACK; GLenum readBuffer = GL_BACK; GLenum texEnvMode[QD3D12_MaxTextureUnits] = {}; GLenum texCombineRGB[QD3D12_MaxTextureUnits] = {}; GLenum texCombineAlpha[QD3D12_MaxTextureUnits] = {}; GLenum texSource0RGB[QD3D12_MaxTextureUnits] = {}; GLenum texSource1RGB[QD3D12_MaxTextureUnits] = {}; GLenum texSource0Alpha[QD3D12_MaxTextureUnits] = {}; GLenum texSource1Alpha[QD3D12_MaxTextureUnits] = {}; GLenum texOperand0RGB[QD3D12_MaxTextureUnits] = {}; GLenum texOperand1RGB[QD3D12_MaxTextureUnits] = {}; GLenum texOperand0Alpha[QD3D12_MaxTextureUnits] = {}; GLenum texOperand1Alpha[QD3D12_MaxTextureUnits] = {}; GLfloat texRGBScale[QD3D12_MaxTextureUnits] = {}; GLfloat texAlphaScale[QD3D12_MaxTextureUnits] = {}; GLfloat texEnvColor[QD3D12_MaxTextureUnits][4] = {}; GLint viewportX = 0; GLint viewportY = 0; GLsizei viewportW = 640; GLsizei viewportH = 480; GLuint boundTexture[QD3D12_MaxTextureUnits] = {}; GLuint activeTextureUnit = 0; GLenum currentPrim = 0; bool inBeginEnd = false; float curU[QD3D12_MaxTextureUnits] = {}; float curV[QD3D12_MaxTextureUnits] = {}; float curColor[4] = { 1, 1, 1, 1 }; float curNormal[3] = { 0, 0, 1 }; float curTangent[3] = { 1, 0, 0 }; float curBinormal[3] = { 0, 1, 0 }; GLuint currentNormalMapTexture = 0; float currentNormalMapStrength = 2.0f; float currentNormalMapYSign = 1.0f; GLuint currentNeuralPOMTexture = 0; bool neuralPOMEnabled = true; GLuint currentGlowMapTexture = 0; float currentGlowMapStrength = 1.0f; GLuint currentSpecularMapTexture = 0; float currentSpecularMapStrength = 1.0f; ImmediateVertexBuffer immediateVerts; GLenum matrixMode = GL_MODELVIEW; std::vector modelStack{ Mat4::Identity() }; std::vector projStack{ Mat4::Identity() }; std::vector texStack[QD3D12_MaxTextureUnits] = { { Mat4::Identity() }, { Mat4::Identity() } }; std::unordered_map textures; GLuint nextTextureId = 1; UINT nextSrvIndex = 1; ComPtr factory; ComPtr device; ComPtr queue; ComPtr srvHeap; UINT srvStride = 0; ComPtr cmdList; ComPtr fence; HANDLE fenceEvent = nullptr; ComPtr rootSig; ComPtr postRootSig; ComPtr postCopyPSO; ComPtr postSharpenPSO; ComPtr postToneMapPSO; ComPtr postToneMapTileMaxPSO; ComPtr postToneMapFinalMaxPSO; ComPtr postTaaPSO; ComPtr postAdditivePSO; ComPtr postDepthCopyPSO; ComPtr postDepthResolveMsaaPSO; ComPtr postGBufferPointResolveMsaaPSO; ComPtr vsMainBlob; ComPtr hsMainBlob; ComPtr dsMainBlob; ComPtr psMainBlob; ComPtr psAlphaBlob; ComPtr psUntexturedBlob; ComPtr psMainColorOnlyBlob; ComPtr psAlphaColorOnlyBlob; ComPtr psUntexturedColorOnlyBlob; ComPtr postVsBlob; ComPtr postPsCopyBlob; ComPtr postPsSharpenBlob; ComPtr postPsToneMapBlob; ComPtr postPsToneMapTileMaxBlob; ComPtr postPsToneMapFinalMaxBlob; ComPtr postPsTaaBlob; ComPtr postPsAddBlob; ComPtr postPsDepthCopyBlob; ComPtr postPsDepthResolveMsaaBlob; ComPtr postPsGBufferPointResolveMsaaBlob; TextureResource whiteTexture; GLenum defaultMinFilter = GL_LINEAR; GLenum defaultMagFilter = GL_LINEAR; GLenum defaultWrapS = GL_REPEAT; GLenum defaultWrapT = GL_REPEAT; GLState() { for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { texture2D[i] = (i == 0); if (texStack[i].empty()) texStack[i].push_back(Mat4::Identity()); texEnvMode[i] = GL_MODULATE; texCombineRGB[i] = GL_MODULATE; texCombineAlpha[i] = GL_MODULATE; texSource0RGB[i] = GL_TEXTURE; texSource1RGB[i] = GL_PREVIOUS_ARB; texSource0Alpha[i] = GL_TEXTURE; texSource1Alpha[i] = GL_PREVIOUS_ARB; texOperand0RGB[i] = GL_SRC_COLOR; texOperand1RGB[i] = GL_SRC_COLOR; texOperand0Alpha[i] = GL_SRC_ALPHA; texOperand1Alpha[i] = GL_SRC_ALPHA; texRGBScale[i] = 1.0f; texAlphaScale[i] = 1.0f; texEnvColor[i][0] = 0.0f; texEnvColor[i][1] = 0.0f; texEnvColor[i][2] = 0.0f; texEnvColor[i][3] = 0.0f; } } }; static GLState g_gl; static std::unordered_map g_windows; QD3D12Window* g_currentWindow = nullptr; static std::unordered_map g_qd3d12RaytracingMeshMaterialFlags; struct QD3D12AutoCameraHistory { bool haveLastCamera = false; bool haveFramePrevious = false; uint64_t frameSerial = UINT64_MAX; float lastViewToClip[16] = {}; float lastWorldToView[16] = {}; float framePreviousViewToClip[16] = {}; float framePreviousWorldToView[16] = {}; }; static QD3D12AutoCameraHistory g_qd3d12AutoCamera; static void QD3D12_MatrixIdentity(float* m) { memset(m, 0, sizeof(float) * 16); m[0] = 1.0f; m[5] = 1.0f; m[10] = 1.0f; m[15] = 1.0f; } static bool QD3D12_MatrixFinite(const float* m) { if (!m) return false; for (int i = 0; i < 16; ++i) { if (!std::isfinite(m[i])) return false; } return true; } static void QD3D12_MatrixCopy(float* dst, const float* src) { memcpy(dst, src, sizeof(float) * 16); } static void QD3D12_MatrixMultiplyCM(const float* a, const float* b, float* out) { float r[16]; for (int col = 0; col < 4; ++col) { for (int row = 0; row < 4; ++row) { float v = 0.0f; for (int k = 0; k < 4; ++k) v += a[k * 4 + row] * b[col * 4 + k]; r[col * 4 + row] = v; } } memcpy(out, r, sizeof(r)); } static bool QD3D12_MatrixInvertCM(const float* m, float* out) { if (!QD3D12_MatrixFinite(m) || !out) return false; float a[4][8]; for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) a[r][c] = m[c * 4 + r]; for (int c = 0; c < 4; ++c) a[r][4 + c] = (r == c) ? 1.0f : 0.0f; } for (int col = 0; col < 4; ++col) { int pivot = col; float best = fabsf(a[col][col]); for (int r = col + 1; r < 4; ++r) { const float v = fabsf(a[r][col]); if (v > best) { best = v; pivot = r; } } if (best <= 1.0e-8f) return false; if (pivot != col) { for (int c = 0; c < 8; ++c) { const float tmp = a[col][c]; a[col][c] = a[pivot][c]; a[pivot][c] = tmp; } } const float invPivot = 1.0f / a[col][col]; for (int c = 0; c < 8; ++c) a[col][c] *= invPivot; for (int r = 0; r < 4; ++r) { if (r == col) continue; const float f = a[r][col]; if (f == 0.0f) continue; for (int c = 0; c < 8; ++c) a[r][c] -= f * a[col][c]; } } for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) out[c * 4 + r] = a[r][4 + c]; } return QD3D12_MatrixFinite(out); } static bool QD3D12_IsPerspectiveProjectionCM(const float* projection) { if (!QD3D12_MatrixFinite(projection)) return false; // OpenGL perspective matrices have m[11] = -1 and m[15] = 0 in column-major // storage. Orthographic/UI passes must not replace the real game camera used // by DLSS Ray Reconstruction. return fabsf(projection[11]) > 0.5f && fabsf(projection[15]) < 1.0e-4f; } static void QD3D12_ConvertGLProjectionToD3DClipCM(const float* glProjection, float* d3dProjection) { QD3D12_MatrixCopy(d3dProjection, glProjection); // The raster shader does: clip.z = 0.5 * (clip.z + clip.w). Bake that same // GL [-w,+w] to D3D [0,+w] depth remap into the matrix given to Streamline. for (int col = 0; col < 4; ++col) { const int z = col * 4 + 2; const int w = col * 4 + 3; d3dProjection[z] = 0.5f * (glProjection[z] + glProjection[w]); } } static void QD3D12_Normalize3(float* v, const float* fallback) { float lenSq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; if (lenSq <= 1.0e-8f || !std::isfinite(lenSq)) { v[0] = fallback[0]; v[1] = fallback[1]; v[2] = fallback[2]; return; } const float invLen = 1.0f / sqrtf(lenSq); v[0] *= invLen; v[1] *= invLen; v[2] *= invLen; } static void QD3D12_DeriveProjectionScalars( const float* glProjection, float fallbackAspect, float* outNearPlane, float* outFarPlane, float* outVerticalFovRadians, float* outAspectRatio) { float nearPlane = 0.01f; float farPlane = 4096.0f; float verticalFov = 1.0471975512f; float aspectRatio = (fallbackAspect > 0.0f) ? fallbackAspect : 1.0f; const float xScale = fabsf(glProjection[0]); const float yScale = fabsf(glProjection[5]); if (yScale > 1.0e-6f) verticalFov = 2.0f * atanf(1.0f / yScale); if (xScale > 1.0e-6f && yScale > 1.0e-6f) aspectRatio = yScale / xScale; const float A = glProjection[10]; const float B = glProjection[14]; const float nDenom = A - 1.0f; const float fDenom = A + 1.0f; if (fabsf(nDenom) > 1.0e-6f && fabsf(fDenom) > 1.0e-6f) { const float n = B / nDenom; const float f = B / fDenom; if (std::isfinite(n) && std::isfinite(f) && n > 0.0f && f > n) { nearPlane = n; farPlane = f; } } *outNearPlane = nearPlane; *outFarPlane = farPlane; *outVerticalFovRadians = verticalFov; *outAspectRatio = aspectRatio; } static void QD3D12_ResetAutoCameraHistory() { g_qd3d12AutoCamera = QD3D12AutoCameraHistory{}; g_gl.cameraState.valid = false; } static bool QD3D12_UpdateCameraInfoFromCurrentMatrices() { if (g_gl.projStack.empty() || g_gl.modelStack.empty()) return false; const float* glProjection = g_gl.projStack.back().m; const float* modelView = g_gl.modelStack.back().m; const float* modelToWorld = g_gl.modelMatrix.m; if (!QD3D12_IsPerspectiveProjectionCM(glProjection) || !QD3D12_MatrixFinite(modelView) || !QD3D12_MatrixFinite(modelToWorld)) { return false; } float worldToModel[16]; if (!QD3D12_MatrixInvertCM(modelToWorld, worldToModel)) return false; float worldToView[16]; QD3D12_MatrixMultiplyCM(modelView, worldToModel, worldToView); float viewToClip[16]; QD3D12_ConvertGLProjectionToD3DClipCM(glProjection, viewToClip); float clipToView[16]; if (!QD3D12_MatrixInvertCM(viewToClip, clipToView)) return false; float viewToWorld[16]; if (!QD3D12_MatrixInvertCM(worldToView, viewToWorld)) return false; if (!g_qd3d12AutoCamera.haveFramePrevious || g_qd3d12AutoCamera.frameSerial != g_gl.frameSerial) { if (!g_qd3d12AutoCamera.haveLastCamera || g_gl.motionHistoryReset) { QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousViewToClip, viewToClip); QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousWorldToView, worldToView); } else { QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousViewToClip, g_qd3d12AutoCamera.lastViewToClip); QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousWorldToView, g_qd3d12AutoCamera.lastWorldToView); } g_qd3d12AutoCamera.frameSerial = g_gl.frameSerial; g_qd3d12AutoCamera.haveFramePrevious = true; } float currentClipToWorld[16]; QD3D12_MatrixMultiplyCM(viewToWorld, clipToView, currentClipToWorld); float previousClipToView[16]; float previousViewToWorld[16]; if (!QD3D12_MatrixInvertCM(g_qd3d12AutoCamera.framePreviousViewToClip, previousClipToView) || !QD3D12_MatrixInvertCM(g_qd3d12AutoCamera.framePreviousWorldToView, previousViewToWorld)) { QD3D12_MatrixIdentity(previousClipToView); QD3D12_MatrixCopy(previousViewToWorld, viewToWorld); } float previousClipToWorld[16]; QD3D12_MatrixMultiplyCM(previousViewToWorld, previousClipToView, previousClipToWorld); float worldToCurrentClip[16]; QD3D12_MatrixMultiplyCM(viewToClip, worldToView, worldToCurrentClip); float worldToPreviousClip[16]; QD3D12_MatrixMultiplyCM( g_qd3d12AutoCamera.framePreviousViewToClip, g_qd3d12AutoCamera.framePreviousWorldToView, worldToPreviousClip); float clipToPrevClip[16]; float prevClipToClip[16]; QD3D12_MatrixMultiplyCM(worldToPreviousClip, currentClipToWorld, clipToPrevClip); QD3D12_MatrixMultiplyCM(worldToCurrentClip, previousClipToWorld, prevClipToClip); float cameraPos[3] = { viewToWorld[12], viewToWorld[13], viewToWorld[14] }; float cameraRight[3] = { viewToWorld[0], viewToWorld[1], viewToWorld[2] }; float cameraUp[3] = { viewToWorld[4], viewToWorld[5], viewToWorld[6] }; float cameraForward[3] = { -viewToWorld[8], -viewToWorld[9], -viewToWorld[10] }; const float rightFallback[3] = { 1.0f, 0.0f, 0.0f }; const float upFallback[3] = { 0.0f, 1.0f, 0.0f }; const float forwardFallback[3] = { 0.0f, 0.0f, -1.0f }; QD3D12_Normalize3(cameraRight, rightFallback); QD3D12_Normalize3(cameraUp, upFallback); QD3D12_Normalize3(cameraForward, forwardFallback); float fallbackAspect = 1.0f; if (g_currentWindow && g_currentWindow->renderHeight > 0) fallbackAspect = (float)g_currentWindow->renderWidth / (float)g_currentWindow->renderHeight; float nearPlane = 0.01f; float farPlane = 4096.0f; float verticalFovRadians = 1.0471975512f; float aspectRatio = fallbackAspect; QD3D12_DeriveProjectionScalars( glProjection, fallbackAspect, &nearPlane, &farPlane, &verticalFovRadians, &aspectRatio); QD3D12_SetCameraInfo( viewToClip, clipToView, clipToPrevClip, prevClipToClip, cameraPos, cameraRight, cameraUp, cameraForward, nearPlane, farPlane, verticalFovRadians, aspectRatio); QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastViewToClip, viewToClip); QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastWorldToView, worldToView); g_qd3d12AutoCamera.haveLastCamera = true; return true; } static inline uint32_t QD3D12_ClampRayMaterialFlags(uint32_t flags) { return flags & GL_RAYTRACING_MATERIAL_FLAG_MASK_QD3D12; } static inline bool QD3D12_RayMaterialFlagsHaveGlass(uint32_t flags) { return (QD3D12_ClampRayMaterialFlags(flags) & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u; } static inline uint32_t QD3D12_CurrentRayMaterialFlags() { return QD3D12_ClampRayMaterialFlags(g_gl.currentRayMaterialFlags); } static inline bool QD3D12_PipelineUsesAlphaBlend(PipelineMode mode) { return mode == PIPE_BLEND_TEX || mode == PIPE_BLEND_UNTEX; } static inline bool QD3D12_CurrentDrawUsesAlphaBlend() { if (!g_gl.blend) return false; // GL_BLEND with this pair is effectively a straight source overwrite. Treat // it as opaque so old code that leaves GL_BLEND enabled by accident does not // silently become ray-transparent. if (g_gl.blendSrc == GL_ONE && g_gl.blendDst == GL_ZERO) return false; return true; } static inline uint32_t QD3D12_CurrentEffectiveRayMaterialFlags() { uint32_t flags = QD3D12_CurrentRayMaterialFlags(); // Alpha-blended fixed-function surfaces are visually transparent. Mirror that // into the DXR material bits automatically so callers do not also have to tag // the same draw as glass just to make shadow/GI rays continue through it. if (QD3D12_CurrentDrawUsesAlphaBlend()) flags |= GL_RAYTRACING_MATERIAL_FLAG_GLASS; return QD3D12_ClampRayMaterialFlags(flags); } static inline float QD3D12_CurrentEffectiveGeometryFlag() { uint32_t bits = (uint32_t)max(0.0f, floorf(g_gl.currentGeometryFlag + 0.5f)); if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentEffectiveRayMaterialFlags())) bits |= QD3D12_GEOMETRY_FLAG_GLASS_BIT; return (float)bits; } static inline float QD3D12_CurrentEffectiveMaterialType() { if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentEffectiveRayMaterialFlags()) && g_gl.currentMaterialType == 0.0f) { return QD3D12_MATERIAL_TYPE_GLASS; } return g_gl.currentMaterialType; } static inline uint32_t QD3D12_EncodeRaytracingInstanceId(uint32_t userInstanceId, uint32_t materialFlags) { return (userInstanceId & QD3D12_RT_INSTANCE_USER_ID_MASK) | ((QD3D12_ClampRayMaterialFlags(materialFlags) & 0xFFu) << QD3D12_RT_INSTANCE_MATERIAL_SHIFT); } static inline uint32_t QD3D12_GetRaytracingMeshMaterialFlags(uint32_t meshHandle) { auto it = g_qd3d12RaytracingMeshMaterialFlags.find(meshHandle); return (it != g_qd3d12RaytracingMeshMaterialFlags.end()) ? it->second : 0u; } static UINT g_qd3d12GBufferSampleCount = 1; static UINT QD3D12_GBufferSampleCount() { (void)g_qd3d12GBufferSampleCount; return 1u; } static bool QD3D12_GBufferMsaaEnabled() { return false; } static D3D12_CPU_DESCRIPTOR_HANDLE QD3D12_RtvAt(QD3D12Window& w, UINT group, UINT frameIndex) { D3D12_CPU_DESCRIPTOR_HANDLE h = w.rtvHeap->GetCPUDescriptorHandleForHeapStart(); h.ptr += SIZE_T(group * QD3D12_FrameCount + frameIndex) * SIZE_T(w.rtvStride); return h; } void QD3D12_SetCurrentWindow(struct QD3D12Window* window) { if (window != g_currentWindow && g_gl.frameOpen && g_gl.frameOwner && g_gl.frameOwner != window) { QD3D12_SubmitOpenFrameNoPresentAndWait(); } g_currentWindow = window; if (window && window->hdc) g_qd3d12CurrentDC = window->hdc; } static GLuint g_lightingTextureId = 0; static TextureResource* g_lightingTexture = nullptr; static D3D12_RESOURCE_STATES g_lightingTextureState = D3D12_RESOURCE_STATE_COMMON; static ComPtr g_qd3d12NeuralPOMZeroBuffer; static UINT g_qd3d12NeuralPOMZeroSrvIndex = UINT_MAX; static D3D12_CPU_DESCRIPTOR_HANDLE g_qd3d12NeuralPOMZeroSrvCpu{}; static D3D12_GPU_DESCRIPTOR_HANDLE g_qd3d12NeuralPOMZeroSrvGpu{}; static inline const GLVertex* QD3D12_GetBatchVertices(const QueuedBatch& batch) { return g_gl.frameVerts.buffer + batch.firstVertex; } static void QD3D12_InitFrameVertexArena() { g_gl.frameVerts.capacity = GL_FRAME_VERTEX_CAPACITY; g_gl.frameVerts.cursor = 0; g_gl.frameVerts.buffer = (GLVertex*)_aligned_malloc(sizeof(GLVertex) * g_gl.frameVerts.capacity, 64); assert(g_gl.frameVerts.buffer); } static void QD3D12_ShutdownFrameVertexArena() { if (g_gl.frameVerts.buffer) { _aligned_free(g_gl.frameVerts.buffer); g_gl.frameVerts.buffer = nullptr; } g_gl.frameVerts.capacity = 0; g_gl.frameVerts.cursor = 0; } static inline void QD3D12_ResetFrameVertexArena() { g_gl.frameVerts.cursor = 0; } static inline GLVertex* QD3D12_AllocFrameVertices(size_t count, size_t* outFirstVertex) { if (count == 0) { if (outFirstVertex) *outFirstVertex = 0; return nullptr; } const size_t start = g_gl.frameVerts.cursor; const size_t end = start + count; if (end > g_gl.frameVerts.capacity) { assert(!"Frame vertex arena overflow"); return nullptr; } g_gl.frameVerts.cursor = end; if (outFirstVertex) *outFirstVertex = start; return g_gl.frameVerts.buffer + start; } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentPositionRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_POSITION_RENDER, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentVelocityRTV() { QD3D12Window& w = *g_currentWindow; 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 CurrentSpecularRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_SPECULAR_RENDER, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedSceneColorRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RESOLVED, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedNormalRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_NORMAL_RESOLVED, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedPositionRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_POSITION_RESOLVED, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentResolvedVelocityRTV() { QD3D12Window& w = *g_currentWindow; 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 CurrentResolvedSpecularRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_SPECULAR_RESOLVED, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_BACKBUFFER, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentTaaOutputRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_TAA_OUTPUT, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentToneMapTileMaxRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_TILE_MAX, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentToneMapSceneMaxRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_SCENE_MAX, w.frameIndex); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentSceneDepthDSV() { return g_currentWindow->dsvHeap->GetCPUDescriptorHandleForHeapStart(); } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentNativeDepthDSV() { D3D12_CPU_DESCRIPTOR_HANDLE h = g_currentWindow->dsvHeap->GetCPUDescriptorHandleForHeapStart(); const UINT dsvStride = g_gl.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); h.ptr += SIZE_T(dsvStride); return h; } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentSceneMsaaDepthDSV() { D3D12_CPU_DESCRIPTOR_HANDLE h = g_currentWindow->dsvHeap->GetCPUDescriptorHandleForHeapStart(); const UINT dsvStride = g_gl.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); h.ptr += SIZE_T(dsvStride) * 2; return h; } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentActiveSceneDepthDSV() { return QD3D12_GBufferMsaaEnabled() ? CurrentSceneMsaaDepthDSV() : CurrentSceneDepthDSV(); } static UINT QD3D12_ActiveRasterWidth(const QD3D12Window& w) { return (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE) ? w.width : w.renderWidth; } static UINT QD3D12_ActiveRasterHeight(const QD3D12Window& w) { return (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE) ? w.height : w.renderHeight; } static GLBufferObject* QD3D12_GetBuffer(GLuint id) { if (id == 0) return nullptr; auto it = g_gl.buffers.find(id); if (it == g_gl.buffers.end()) return nullptr; return &it->second; } static TextureResource* QD3D12_FindTextureResource(GLuint id) { if (id == 0) return nullptr; auto it = g_gl.textures.find(id); if (it == g_gl.textures.end()) return nullptr; return &it->second; } static TextureResource& QD3D12_EnsureTextureName(GLuint id) { auto it = g_gl.textures.find(id); if (it != g_gl.textures.end()) return it->second; TextureResource tex{}; tex.glId = id; tex.srvIndex = UINT_MAX; tex.minFilter = g_gl.defaultMinFilter; tex.magFilter = g_gl.defaultMagFilter; tex.wrapS = g_gl.defaultWrapS; tex.wrapT = g_gl.defaultWrapT; return g_gl.textures.emplace(id, std::move(tex)).first->second; } static bool QD3D12_IsTextureTaggedNormalMap(GLuint id) { TextureResource* tex = QD3D12_FindTextureResource(id); return tex && tex->isNormalMap; } static bool QD3D12_IsTextureTaggedGlowMap(GLuint id) { TextureResource* tex = QD3D12_FindTextureResource(id); return tex && tex->isGlowMap; } static bool QD3D12_IsTextureTaggedSpecularMap(GLuint id) { TextureResource* tex = QD3D12_FindTextureResource(id); return tex && tex->isSpecularMap; } static TextureResource* QD3D12_SelectNormalMapTexture(TextureResource* const* allTextures) { // Explicit material binding wins. This lets callers bind a normal map even // before they have tagged it, while glTagTextureNormalMap() enables automatic // discovery from texture units. if (g_gl.currentNormalMapTexture != 0) { TextureResource* explicitNormal = QD3D12_FindTextureResource(g_gl.currentNormalMapTexture); if (explicitNormal) return explicitNormal; } if (allTextures) { for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { TextureResource* tex = allTextures[i]; if (tex && tex != &g_gl.whiteTexture && tex->isNormalMap) return tex; } } for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { TextureResource* tex = QD3D12_FindTextureResource(g_gl.boundTexture[i]); if (tex && tex->isNormalMap) return tex; } 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 TextureResource* QD3D12_SelectSpecularMapTexture(TextureResource* const* allTextures) { // Explicit material binding wins. Tagged specular maps can also be discovered // from any fixed-function texture unit without consuming the diffuse/lightmap slots. if (g_gl.currentSpecularMapTexture != 0) { TextureResource* explicitSpecular = QD3D12_FindTextureResource(g_gl.currentSpecularMapTexture); if (explicitSpecular) { // Avoid common cross-role binding mistakes. A texture tagged only as a // normal or glow map should not silently become specular albedo. if ((explicitSpecular->isNormalMap && !explicitSpecular->isSpecularMap) || (explicitSpecular->isGlowMap && !explicitSpecular->isSpecularMap)) return nullptr; return explicitSpecular; } } if (allTextures) { for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { TextureResource* tex = allTextures[i]; if (tex && tex != &g_gl.whiteTexture && tex->isSpecularMap) return tex; } } for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { TextureResource* tex = QD3D12_FindTextureResource(g_gl.boundTexture[i]); if (tex && tex->isSpecularMap) return tex; } return nullptr; } static const uint8_t* QD3D12_ResolveArrayPointer(const void* ptr) { if (g_gl.boundArrayBuffer != 0) { GLBufferObject* bo = QD3D12_GetBuffer(g_gl.boundArrayBuffer); if (!bo) return nullptr; const size_t offset = (size_t)ptr; if (offset > bo->data.size()) return nullptr; return bo->data.data() + offset; } return reinterpret_cast(ptr); } static const void* QD3D12_ResolveElementPointer(const void* ptr, GLenum indexType, GLsizei count) { if (g_gl.boundElementArrayBuffer != 0) { GLBufferObject* bo = QD3D12_GetBuffer(g_gl.boundElementArrayBuffer); if (!bo) return nullptr; size_t indexSize = 0; switch (indexType) { case GL_UNSIGNED_INT: indexSize = sizeof(GLuint); break; case GL_UNSIGNED_SHORT: indexSize = sizeof(GLushort); break; case GL_UNSIGNED_BYTE: indexSize = sizeof(GLubyte); break; default: return nullptr; } const size_t offset = (size_t)ptr; const size_t bytesNeeded = (size_t)count * indexSize; if (offset > bo->data.size() || bytesNeeded > (bo->data.size() - offset)) return nullptr; return bo->data.data() + offset; } return ptr; } static void QD3D12_RetireResource(ComPtr& res) { if (!res) return; RetiredResource rr{}; rr.resource = res; // If no frame has been submitted yet, force it to live until next GPU idle. UINT64 fenceValue = 0; if (g_gl.frameOpen && g_gl.frameOwner) { fenceValue = g_gl.frameOwner->frames[g_gl.frameOwner->frameIndex].fenceValue; if (fenceValue == 0) fenceValue = g_gl.nextFenceValue; } else { fenceValue = g_gl.nextFenceValue; } rr.fenceValue = fenceValue; g_retiredResources.push_back(std::move(rr)); res.Reset(); } static UINT64 QD3D12_CurrentSubmissionFenceValue() { return g_gl.nextFenceValue; } void QD3D12_CollectRetiredResources() { const UINT64 completed = g_gl.fence ? g_gl.fence->GetCompletedValue() : 0; size_t write = 0; for (size_t read = 0; read < g_retiredResources.size(); ++read) { if (g_retiredResources[read].fenceValue <= completed) { // let ComPtr drop here } else { if (write != read) g_retiredResources[write] = std::move(g_retiredResources[read]); ++write; } } g_retiredResources.resize(write); } // ============================================================ // SECTION 4: shaders // ============================================================ static const char* kQuakeWrapperHLSL = R"HLSL( cbuffer DrawCB : register(b0) { float4x4 gMVP; float4x4 gPrevMVP; float4x4 gModelMatrix; float gAlphaRef; float gUseTex0; float gUseTex1; float gTex1IsLightmap; float gTexEnvMode0; float gTexEnvMode1; float geometryFlag; float gRoughness; float gFogEnabled; float gFogMode; float gFogDensity; float gFogStart; float gFogEnd; float gPointSize; float gMaterialType; float gAlphaFunc; float4 gFogColor; float2 gRenderSize; float2 gInvRenderSize; float2 gJitterPixels; float2 gPrevJitterPixels; float4 gMotionPad; float4 gMaterialMapPad; float4 gTexComb0RGB; float4 gTexComb0Alpha; float4 gTexComb0Operand; float4 gTexEnvColor0; float4 gTexComb1RGB; float4 gTexComb1Alpha; float4 gTexComb1Operand; float4 gTexEnvColor1; float4 gCameraPomPad; float4 gNeuralPomPad; }; #define gUseNormalMap gMotionPad.x #define gNormalMapStrength gMotionPad.y #define gNormalMapYSign gMotionPad.z // Pixel-shader POM depth in UV space. Keep this conservative: legacy idTech // normal maps often do not have a true height channel, so the POM path below // gates fallback height by local detail before applying any offset. #define gParallaxScale (clamp(gNormalMapStrength, 0.0, 4.0) * 0.026) #define gCameraWorldPos gCameraPomPad.xyz #define gCameraPomValid gCameraPomPad.w #define gUseNeuralPOM gNeuralPomPad.x #define gNeuralPOMDebug gNeuralPomPad.y // Runtime Neural POM relief controls. x/y keep the existing enable/debug ABI; // z deepens the learned UV ray offset and w deepens the learned normal slope. // The CPU fills these from glNormalMapStrengthf() so existing material calls // can make Neural POM read deeper without changing the payload layout. #define gNeuralPOMDepthBoost max(gNeuralPomPad.z, 1.0) #define gNeuralPOMNormalBoost max(gNeuralPomPad.w, 1.0) #define QD3D12_NP_DEFAULT_DELTA_SCALE 0.080 #define QD3D12_NP_MAX_DELTA_SCALE 0.220 #define gUseGlowMap gMotionPad.w #define gGlowMapStrength gMotionPad.w #define gUseSpecularMap gMaterialMapPad.x #define gSpecularMapStrength gMaterialMapPad.y #define gAlphaBlendPass gMaterialMapPad.z // Full object-space displacement amplitude for the tessellation domain shader. // The CPU side keeps this conservative, but clamp here too so a bad material // strength cannot explode the mesh. // Raised from the old 0.35 cap so stronger materials can push deeper, while // the hull shader below uses shared-edge tess factors to prevent crack artifacts. #define gTessellationDisplacement clamp(gMaterialMapPad.w, 0.0, 0.50) #define QD3D12_TESS_MIN_FACTOR 1.0 #define QD3D12_TESS_NEAR_MIN_FACTOR 2.0 #define QD3D12_TESS_MAX_FACTOR 15.0 #define QD3D12_TESS_TARGET_EDGE_PIXELS 28.0 #define QD3D12_TESS_DISTANCE_NEAR 512.0 #define QD3D12_TESS_DISTANCE_FAR 2200.0 #define QD3D12_POM_DISTANCE_NEAR 384.0 #define QD3D12_POM_DISTANCE_FAR 1800.0 Texture2D gTex0 : register(t0); Texture2D gTex1 : register(t1); Texture2D gNormalMap : register(t2); Texture2D gGlowMap : register(t3); Texture2D gSpecularMap : register(t4); ByteAddressBuffer gNeuralPomWeights : register(t5); ByteAddressBuffer gNeuralPomLatent : register(t6); SamplerState gSamp0 : register(s0); SamplerState gSamp1 : register(s1); SamplerState gSamp2 : register(s2); SamplerState gSamp3 : register(s3); SamplerState gSamp4 : register(s4); struct VSIn { float3 pos : POSITION; float3 normal : NORMAL; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 col : COLOR0; float3 tangent : TANGENT; float3 binormal : BINORMAL; }; struct VSOut { float4 pos : SV_Position; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 col : COLOR0; float fogCoord : TEXCOORD2; float3 worldPos : TEXCOORD3; float3 normal : TEXCOORD4; float3 tangent : TEXCOORD5; float3 binormal : TEXCOORD6; float4 attr : TEXCOORD7; float4 prevClip : TEXCOORD8; float4 currClip : TEXCOORD9; float3 objPos : TEXCOORD10; float3 objNormal : TEXCOORD11; float psize : PSIZE; }; struct PSOut { float4 color : SV_Target0; float4 normal : SV_Target1; float4 position : SV_Target2; float4 velocity : SV_Target3; float4 emissive : SV_Target4; float4 specular : SV_Target5; }; float4 QD3D12_GetTexEnvSource(float source, float4 texel, float4 primary, float4 previous, float4 constantColor) { if (source < 0.5) return texel; else if (source < 1.5) return primary; else if (source < 2.5) return previous; else return constantColor; } float3 QD3D12_ApplyRgbOperand(float4 v, float operand) { if (operand < 0.5) return v.rgb; else if (operand < 1.5) return float3(1.0, 1.0, 1.0) - v.rgb; else if (operand < 2.5) return v.aaa; else return float3(1.0, 1.0, 1.0) - v.aaa; } float QD3D12_ApplyAlphaOperand(float4 v, float operand) { if (operand < 0.5) return v.a; else if (operand < 1.5) return 1.0 - v.a; else if (operand < 2.5) return v.r; else return 1.0 - v.r; } float4 ApplyTexCombine( float4 previous, float4 texel, float4 primary, float mode, float4 rgbDesc, float4 alphaDesc, float4 operandDesc, float4 constantColor) { if (mode < 4.5) { if (mode < 0.5) return previous * texel; else if (mode < 1.5) return texel; else if (mode < 2.5) { float3 rgb = lerp(previous.rgb, texel.rgb, texel.a); return float4(rgb, previous.a); } else if (mode < 3.5) { float3 rgb = lerp(previous.rgb, constantColor.rgb, texel.rgb); return float4(rgb, previous.a * texel.a); } else { return float4(previous.rgb + texel.rgb, previous.a * texel.a); } } float4 s0rgb = QD3D12_GetTexEnvSource(rgbDesc.y, texel, primary, previous, constantColor); float4 s1rgb = QD3D12_GetTexEnvSource(rgbDesc.z, texel, primary, previous, constantColor); float3 a0rgb = QD3D12_ApplyRgbOperand(s0rgb, operandDesc.x); float3 a1rgb = QD3D12_ApplyRgbOperand(s1rgb, operandDesc.y); float3 outRgb; if (rgbDesc.x < 1.5) outRgb = a0rgb; else if (rgbDesc.x < 2.5) outRgb = a0rgb * a1rgb; else if (rgbDesc.x < 3.5) outRgb = a0rgb + a1rgb; else if (rgbDesc.x < 4.5) outRgb = a0rgb + a1rgb - float3(0.5, 0.5, 0.5); else outRgb = a0rgb * a1rgb; outRgb *= max(rgbDesc.w, 1.0); float4 s0a = QD3D12_GetTexEnvSource(alphaDesc.y, texel, primary, previous, constantColor); float4 s1a = QD3D12_GetTexEnvSource(alphaDesc.z, texel, primary, previous, constantColor); float a0 = QD3D12_ApplyAlphaOperand(s0a, operandDesc.z); float a1 = QD3D12_ApplyAlphaOperand(s1a, operandDesc.w); float outA; if (alphaDesc.x < 1.5) outA = a0; else if (alphaDesc.x < 2.5) outA = a0 * a1; else if (alphaDesc.x < 3.5) outA = a0 + a1; else if (alphaDesc.x < 4.5) outA = a0 + a1 - 0.5; else outA = a0 * a1; outA *= max(alphaDesc.w, 1.0); return saturate(float4(outRgb, outA)); } float TinyNoise(int2 p) { uint n = (uint(p.x) * 1973u) ^ (uint(p.y) * 9277u) ^ 0x68bc21ebu; n = (n << 13u) ^ n; return 1.0 - float((n * (n * n * 15731u + 789221u) + 1376312589u) & 0x7fffffffu) / 1073741824.0; } float3 ApplySoftwareRendererLook(float3 c) { c = saturate(c); float luma0 = dot(c, float3(0.299, 0.587, 0.114)); float3 tinted = c * float3(1.01, 1.00, 0.995); float luma1 = dot(tinted, float3(0.299, 0.587, 0.114)); tinted = lerp(luma1.xxx, tinted, 1.10); float luma2 = dot(tinted, float3(0.299, 0.587, 0.114)); if (luma2 > 0.0001) tinted *= (luma0 / luma2); return saturate(tinted); } float ComputeFogFactor(float fogCoord) { if (gFogEnabled < 0.5) return 1.0; float f = 1.0; if (gFogMode < 0.5) { float denom = max(gFogEnd - gFogStart, 0.00001); f = (gFogEnd - fogCoord) / denom; } else if (gFogMode < 1.5) { f = exp(-gFogDensity * fogCoord); } else { float d = gFogDensity * fogCoord; f = exp(-(d * d)); } return saturate(f); } float4 ApplyFog(float4 color, float fogCoord) { float fogFactor = ComputeFogFactor(fogCoord); color.rgb = lerp(gFogColor.rgb, color.rgb, fogFactor); return color; } void QD3D12_AlphaTest(float alpha) { const float eps = 0.00001; if (gAlphaFunc < 0.5) clip(-1.0); else if (gAlphaFunc < 1.5) clip(gAlphaRef - alpha - eps); else if (gAlphaFunc < 2.5) clip(eps - abs(alpha - gAlphaRef)); else if (gAlphaFunc < 3.5) clip(gAlphaRef - alpha + eps); else if (gAlphaFunc < 4.5) clip(alpha - gAlphaRef - eps); else if (gAlphaFunc < 5.5) clip(abs(alpha - gAlphaRef) - eps); else if (gAlphaFunc < 6.5) clip(alpha - gAlphaRef + eps); } float3 QD3D12_SafeNormalize(float3 v, float3 fallback) { float lenSq = dot(v, v); if (lenSq <= 1e-8) return fallback; return v * rsqrt(lenSq); } float3 QD3D12_DecodeTangentSpaceNormal(float3 encodedNormal) { float3 n = encodedNormal * 2.0 - 1.0; n.y *= gNormalMapYSign; n.xy *= max(gNormalMapStrength, 0.0); return QD3D12_SafeNormalize(n, float3(0.0, 0.0, 1.0)); } float QD3D12_HeightFromNormalSample(float4 nm) { // Many legacy normal maps are RGB/DXT uploads with an implicitly forced // opaque alpha channel. Treat alpha as height only when it looks like real // authored data; otherwise derive a stronger pseudo-height from the normal // itself. A flat normal (0.5, 0.5, 1.0) stays centered at 0.5, while steep // normal-map detail now gets a much larger positive lift so tessellation // visibly pops instead of barely moving the silhouette. bool hasAuthoredHeight = (nm.a > 0.0001 && nm.a < 0.999); float3 decoded = nm.xyz * 2.0 - 1.0; float slope = 1.0 - saturate(abs(decoded.z)); float slopeHeight = 0.5 + pow(slope, 0.65) * 0.5; float h = hasAuthoredHeight ? nm.a : slopeHeight; return saturate(h); } float QD3D12_GetHeightFromNormalMap(float2 uv) { return QD3D12_HeightFromNormalSample(gNormalMap.Sample(gSamp2, uv)); } float QD3D12_GetHeightFromNormalMapLOD(float2 uv, float lod) { return QD3D12_HeightFromNormalSample(gNormalMap.SampleLevel(gSamp2, uv, lod)); } float2 QD3D12_NormalMapTexelSize() { uint w = 1; uint h = 1; gNormalMap.GetDimensions(w, h); return rcp(max(float2((float)w, (float)h), float2(1.0, 1.0))); } float QD3D12_GetFilteredTessHeight(float2 uv) { // Hardware tessellation magnifies tiny normal-map compression errors. // A light cross filter keeps the deeper displacement stable without // blurring the color/normal sampling path used by the pixel shader. float2 texel = QD3D12_NormalMapTexelSize(); float center = QD3D12_GetHeightFromNormalMapLOD(uv, 0.0); float hx0 = QD3D12_GetHeightFromNormalMapLOD(uv - float2(texel.x, 0.0), 0.0); float hx1 = QD3D12_GetHeightFromNormalMapLOD(uv + float2(texel.x, 0.0), 0.0); float hy0 = QD3D12_GetHeightFromNormalMapLOD(uv - float2(0.0, texel.y), 0.0); float hy1 = QD3D12_GetHeightFromNormalMapLOD(uv + float2(0.0, texel.y), 0.0); return saturate(center * 0.50 + (hx0 + hx1 + hy0 + hy1) * 0.125); } float QD3D12_CleanCenteredTessHeight(float height) { // Suppress barely-visible single-texel noise before applying the now deeper // displacement. This removes acne-like spikes while preserving real relief. float centered = (height - 0.5) * 2.0; float s = (centered < 0.0) ? -1.0 : 1.0; float a = saturate((abs(centered) - 0.015) * (1.0 / 0.985)); return s * a; } float3 QD3D12_BuildFallbackTangent(float3 n) { float3 up = (abs(n.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0); return QD3D12_SafeNormalize(cross(up, n), float3(1.0, 0.0, 0.0)); } void QD3D12_BuildTBN(VSOut i, out float3 n, out float3 t, out float3 b) { n = QD3D12_SafeNormalize(i.normal, float3(0.0, 0.0, 1.0)); float3 tFallback = QD3D12_BuildFallbackTangent(n); t = i.tangent - n * dot(i.tangent, n); t = QD3D12_SafeNormalize(t, tFallback); float3 bFallback = QD3D12_SafeNormalize(cross(n, t), float3(0.0, 1.0, 0.0)); b = i.binormal - n * dot(i.binormal, n) - t * dot(i.binormal, t); b = QD3D12_SafeNormalize(b, bFallback); } void QD3D12_BuildPixelTBN(VSOut i, out float3 n, out float3 t, out float3 b) { // Prefer a per-pixel UV-derived basis. A lot of legacy paths do not provide // reliable tangent/binormal arrays, and that was a major cause of POM moving // sideways on some walls/floors. The fallback still handles degenerate UVs. QD3D12_BuildTBN(i, n, t, b); float3 dpdx = ddx(i.worldPos); float3 dpdy = ddy(i.worldPos); float2 duvdx = ddx(i.uv0); float2 duvdy = ddy(i.uv0); float det = duvdx.x * duvdy.y - duvdx.y * duvdy.x; if (abs(det) <= 1.0e-7) return; float invDet = 1.0 / det; float3 derivedT = (dpdx * duvdy.y - dpdy * duvdx.y) * invDet; float3 derivedB = (dpdy * duvdx.x - dpdx * duvdy.x) * invDet; derivedT = derivedT - n * dot(n, derivedT); derivedT = QD3D12_SafeNormalize(derivedT, t); derivedB = derivedB - n * dot(n, derivedB) - derivedT * dot(derivedT, derivedB); derivedB = QD3D12_SafeNormalize(derivedB, QD3D12_SafeNormalize(cross(n, derivedT), b)); t = derivedT; b = derivedB; } float QD3D12_AuthoredPomAlphaWeight(float alphaValue) { // Alpha == 1 is frequently forced by RGB/DXT uploads in this shim. Treat only // non-trivial alpha as an authored height channel. return (alphaValue > 0.0001 && alphaValue < 0.999) ? 1.0 : 0.0; } float QD3D12_Luma(float3 c) { return dot(saturate(c), float3(0.299, 0.587, 0.114)); } float QD3D12_GetPomHeightFromSamples(float2 uv) { float4 nm = gNormalMap.SampleLevel(gSamp2, uv, 0.0); float authored = QD3D12_AuthoredPomAlphaWeight(nm.a); if (authored > 0.5) return saturate(nm.a); // RGB-only normal maps do not contain absolute height. Use a conservative // pseudo-height from the normal-map slope. This is intentionally centered // and local-detail-gated below; otherwise a uniformly tilted normal map would // slide the whole diffuse texture, which was the main visible offset bug. float3 decoded = nm.xyz * 2.0 - 1.0; decoded.y *= gNormalMapYSign; float slope = saturate(length(decoded.xy)); float normalHeight = saturate(0.5 + (pow(slope, 0.80) - 0.35) * 0.42); // Optional diffuse assist, but only when the normal map itself has some // actual slope. This helps old RGB-only bump maps read without letting a // plain diffuse texture behave like a fake height map by itself. if (gUseTex0 > 0.5) { float normalDetailGate = saturate((slope - 0.040) * 8.0); if (normalDetailGate > 0.001) { float2 texel = QD3D12_NormalMapTexelSize(); float lumC = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv, 0.0).rgb); float lumL = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(texel.x, 0.0), 0.0).rgb); float lumR = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(texel.x, 0.0), 0.0).rgb); float lumU = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(0.0, texel.y), 0.0).rgb); float lumD = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(0.0, texel.y), 0.0).rgb); float lumAvg = (lumL + lumR + lumU + lumD) * 0.25; float localContrast = abs(lumC - lumAvg); float diffuseWeight = saturate((localContrast - 0.018) * 16.0) * normalDetailGate; // Local contrast only; never absolute brightness. Keep it subtle so // brick/grate detail can pop, but signs/posters do not shear sideways. float diffuseHeight = saturate(0.5 + (lumC - lumAvg) * 1.85); normalHeight = lerp(normalHeight, diffuseHeight, diffuseWeight * 0.28); } } return saturate(normalHeight); } )HLSL" R"HLSL( void QD3D12_GetPomHeightStats(float2 uv, out float minHeight, out float maxHeight, out float authoredWeight) { float2 texel = QD3D12_NormalMapTexelSize(); float4 nmC = gNormalMap.SampleLevel(gSamp2, uv, 0.0); float4 nmL = gNormalMap.SampleLevel(gSamp2, uv - float2(texel.x, 0.0), 0.0); float4 nmR = gNormalMap.SampleLevel(gSamp2, uv + float2(texel.x, 0.0), 0.0); float4 nmU = gNormalMap.SampleLevel(gSamp2, uv - float2(0.0, texel.y), 0.0); float4 nmD = gNormalMap.SampleLevel(gSamp2, uv + float2(0.0, texel.y), 0.0); authoredWeight = max( QD3D12_AuthoredPomAlphaWeight(nmC.a), max( max(QD3D12_AuthoredPomAlphaWeight(nmL.a), QD3D12_AuthoredPomAlphaWeight(nmR.a)), max(QD3D12_AuthoredPomAlphaWeight(nmU.a), QD3D12_AuthoredPomAlphaWeight(nmD.a)))); float hC = QD3D12_GetPomHeightFromSamples(uv); float hL = QD3D12_GetPomHeightFromSamples(uv - float2(texel.x, 0.0)); float hR = QD3D12_GetPomHeightFromSamples(uv + float2(texel.x, 0.0)); float hU = QD3D12_GetPomHeightFromSamples(uv - float2(0.0, texel.y)); float hD = QD3D12_GetPomHeightFromSamples(uv + float2(0.0, texel.y)); minHeight = min(hC, min(min(hL, hR), min(hU, hD))); maxHeight = max(hC, max(max(hL, hR), max(hU, hD))); } float QD3D12_GetPomDepth(float2 uv) { return saturate(1.0 - QD3D12_GetPomHeightFromSamples(uv)); } float QD3D12_GetPomConfidence(float2 uv) { float minHeight = 0.5; float maxHeight = 0.5; float authoredWeight = 0.0; QD3D12_GetPomHeightStats(uv, minHeight, maxHeight, authoredWeight); // Authored alpha height can use smaller height deltas. RGB-derived height // needs more local range because it is estimated from a normal map, not a // true displacement texture. float heightRange = maxHeight - minHeight; float threshold = lerp(0.055, 0.014, authoredWeight); float confidence = saturate((heightRange - threshold) / max(0.18 - threshold, 0.001)); // Make the transition less binary so low-detail areas fade instead of popping. return confidence * confidence * (3.0 - 2.0 * confidence); } // Keep the runtime network bounded. Most payloads do not need the full 128-wide // MLP in a fixed-function material pass; clamping evaluation here prevents a // single Neural POM material from turning every pixel into a huge ByteAddressBuffer // matrix multiply. Weight offsets still use the source hidden size, so payloads // with wider models remain layout-compatible; we just evaluate the leading slice. #define QD3D12_NP_MAX_EVAL_HIDDEN 64u struct QD3D12NeuralPOMResult { float active; float2 uv; float depth; float3 normalTS; float confidence; float visibility; float3 albedo; float3 specular; float3 basisN; float3 basisT; float3 basisB; }; uint QD3D12_NP_LoadU32(uint byteOffset) { return gNeuralPomWeights.Load(byteOffset); } float QD3D12_NP_LoadF32(uint byteOffset) { return asfloat(gNeuralPomWeights.Load(byteOffset)); } float QD3D12_NP_Weight(uint floatIndex) { // The trainer writes a 32-byte header, then all MLP floats. return QD3D12_NP_LoadF32(32u + floatIndex * 4u); } uint QD3D12_NP_ReadLatentU16(uint byteOffset) { uint aligned = byteOffset & ~3u; uint word = gNeuralPomLatent.Load(aligned); return ((byteOffset & 2u) != 0u) ? ((word >> 16u) & 0xFFFFu) : (word & 0xFFFFu); } float QD3D12_NP_ReadLatentHalf(uint byteOffset) { return f16tof32(QD3D12_NP_ReadLatentU16(byteOffset)); } float QD3D12_NP_Tanh(float x) { // Fast odd tanh approximation. The old path evaluated exp() for every hidden // neuron in both MLP layers, which dominated the pixel shader cost. This form // is smooth, bounded, and close enough for a POM inference signal. x = clamp(x, -3.0, 3.0); float x2 = x * x; return clamp(x * (27.0 + x2) / (27.0 + 9.0 * x2), -1.0, 1.0); } float QD3D12_NP_Sigmoid(float x) { // Reuse the fast tanh approximation; output activations are a tiny part of the // work, but this keeps the whole Neural POM eval free of per-neuron exp(). return saturate(0.5 + 0.5 * QD3D12_NP_Tanh(0.5 * x)); } float QD3D12_NP_ActivateOut(uint index, float z) { if (index == 0u || index == 1u || index == 3u || index == 4u) return QD3D12_NP_Tanh(z); return QD3D12_NP_Sigmoid(z); } float QD3D12_NP_LatentAt(uint x, uint y, uint c, uint latentRes, uint latentChannels) { uint slices = (latentChannels + 3u) >> 2u; uint slice = c >> 2u; uint comp = c & 3u; uint texel = y * latentRes + x; uint halfIndex = ((slice * latentRes * latentRes + texel) * 4u + comp); return QD3D12_NP_ReadLatentHalf(halfIndex * 2u); } void QD3D12_NP_SampleLatent(float2 uv, uint latentRes, uint latentChannels, out float latent[16]) { [unroll] for (uint c = 0u; c < 16u; ++c) latent[c] = 0.0; float2 fuv = frac(uv); float fx = fuv.x * (float)latentRes - 0.5; float fy = fuv.y * (float)latentRes - 0.5; int x0i = (int)floor(fx); int y0i = (int)floor(fy); float tx = fx - (float)x0i; float ty = fy - (float)y0i; uint x0 = (uint)((x0i % (int)latentRes + (int)latentRes) % (int)latentRes); uint y0 = (uint)((y0i % (int)latentRes + (int)latentRes) % (int)latentRes); uint x1 = (x0 + 1u) % latentRes; uint y1 = (y0 + 1u) % latentRes; float w00 = (1.0 - tx) * (1.0 - ty); float w10 = tx * (1.0 - ty); float w01 = (1.0 - tx) * ty; float w11 = tx * ty; [loop] for (uint c = 0u; c < latentChannels && c < 16u; ++c) { float a = QD3D12_NP_LatentAt(x0, y0, c, latentRes, latentChannels); float b = QD3D12_NP_LatentAt(x1, y0, c, latentRes, latentChannels); float d = QD3D12_NP_LatentAt(x0, y1, c, latentRes, latentChannels); float e = QD3D12_NP_LatentAt(x1, y1, c, latentRes, latentChannels); latent[c] = a * w00 + b * w10 + d * w01 + e * w11; } } QD3D12NeuralPOMResult QD3D12_EvaluateNeuralPOM(VSOut i, float2 baseUv) { QD3D12NeuralPOMResult r; r.active = 0.0; r.uv = baseUv; r.depth = 0.0; r.normalTS = float3(0.0, 0.0, 1.0); r.confidence = 0.0; r.visibility = 1.0; r.albedo = float3(1.0, 1.0, 1.0); r.specular = float3(0.0, 0.0, 0.0); r.basisN = QD3D12_SafeNormalize(i.normal, float3(0.0, 0.0, 1.0)); r.basisT = QD3D12_BuildFallbackTangent(r.basisN); r.basisB = QD3D12_SafeNormalize(cross(r.basisN, r.basisT), float3(0.0, 1.0, 0.0)); if (gUseNeuralPOM < 0.5) return r; uint magic = QD3D12_NP_LoadU32(0u); uint version = QD3D12_NP_LoadU32(4u); if (magic != 0x4D504E49u || version != 1u) return r; uint inputCount = min(QD3D12_NP_LoadU32(8u), 32u); uint sourceHidden = min(QD3D12_NP_LoadU32(12u), 128u); uint hidden = min(sourceHidden, QD3D12_NP_MAX_EVAL_HIDDEN); uint outputCount = QD3D12_NP_LoadU32(16u); uint latentRes = QD3D12_NP_LoadU32(20u); uint latentChannels = min(QD3D12_NP_LoadU32(24u), 16u); float deltaScale = QD3D12_NP_LoadF32(28u); if (inputCount == 0u || sourceHidden == 0u || hidden == 0u || outputCount < 13u || latentRes == 0u || latentChannels == 0u) return r; if (latentRes > 4096u) return r; if (abs(deltaScale) <= 1.0e-8) deltaScale = QD3D12_NP_DEFAULT_DELTA_SCALE; deltaScale = clamp(abs(deltaScale), 0.0, QD3D12_NP_MAX_DELTA_SCALE); float3 n, t, b; QD3D12_BuildPixelTBN(i, n, t, b); r.basisN = n; r.basisT = t; r.basisB = b; float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos); float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n); float NoV = max(dot(n, viewWS), 0.0); // Do not run the MLP in cases where POM is going to be invisible anyway. // The previous code still evaluated the whole network after the normal POM // path would have faded out by distance or grazing angle. float viewDistance = (gCameraPomValid >= 0.5) ? max(length(gCameraWorldPos - i.worldPos), 1.0) : max(abs(i.currClip.w), 1.0); float distanceFade = 1.0 - smoothstep(QD3D12_POM_DISTANCE_NEAR, QD3D12_POM_DISTANCE_FAR, viewDistance); float grazingFade = smoothstep(0.08, 0.22, saturate(NoV)); float evalFade = saturate(distanceFade * grazingFade); if (evalFade <= 0.001) return r; float3 viewTS = QD3D12_SafeNormalize(float3(dot(viewWS, t), dot(viewWS, b), NoV), float3(0.0, 0.0, 1.0)); // The current fixed-function path does not carry a per-pixel light vector. // The trained visibility output is still evaluated, but lightTS is neutral. float3 lightTS = float3(0.0, 0.0, 1.0); float latent[16]; QD3D12_NP_SampleLatent(baseUv, latentRes, latentChannels, latent); float input[32]; [unroll] for (uint k = 0u; k < 32u; ++k) input[k] = 0.0; [loop] for (uint c = 0u; c < latentChannels && c < 16u; ++c) input[c] = latent[c]; uint inputBase = latentChannels; input[inputBase + 0u] = viewTS.x; input[inputBase + 1u] = viewTS.y; input[inputBase + 2u] = viewTS.z; input[inputBase + 3u] = lightTS.x; input[inputBase + 4u] = lightTS.y; input[inputBase + 5u] = lightTS.z; input[inputBase + 6u] = clamp(gNormalMapStrength / 4.0, 0.0, 1.0); input[inputBase + 7u] = distanceFade; input[inputBase + 8u] = evalFade; float h1[128]; float h2[128]; float outv[13]; uint offW1 = 0u; uint offB1 = offW1 + sourceHidden * inputCount; uint offW2 = offB1 + sourceHidden; uint offB2 = offW2 + sourceHidden * sourceHidden; uint offW3 = offB2 + sourceHidden; uint offB3 = offW3 + outputCount * sourceHidden; [loop] for (uint j = 0u; j < hidden; ++j) { float z = QD3D12_NP_Weight(offB1 + j); [loop] for (uint k = 0u; k < inputCount; ++k) z += QD3D12_NP_Weight(offW1 + j * inputCount + k) * input[k]; h1[j] = QD3D12_NP_Tanh(z); } [loop] for (uint j = 0u; j < hidden; ++j) { float z = QD3D12_NP_Weight(offB2 + j); [loop] for (uint k = 0u; k < hidden; ++k) z += QD3D12_NP_Weight(offW2 + j * sourceHidden + k) * h1[k]; h2[j] = QD3D12_NP_Tanh(z); } [loop] for (uint o = 0u; o < 13u; ++o) { float z = QD3D12_NP_Weight(offB3 + o); [loop] for (uint k = 0u; k < hidden; ++k) z += QD3D12_NP_Weight(offW3 + o * sourceHidden + k) * h2[k]; outv[o] = QD3D12_NP_ActivateOut(o, z); } float runtimeDeltaScale = min(deltaScale * clamp(gNeuralPOMDepthBoost, 1.0, 3.0), QD3D12_NP_MAX_DELTA_SCALE); float2 deltaUv = clamp(float2(outv[0], outv[1]), -1.0, 1.0) * runtimeDeltaScale * evalFade; r.uv = frac(baseUv + deltaUv); r.depth = saturate(outv[2]); float normalBoost = clamp(gNeuralPOMNormalBoost, 1.0, 2.5); float nx = clamp(outv[3] * normalBoost, -0.98, 0.98); float ny = clamp(outv[4] * normalBoost, -0.98, 0.98); float nz = sqrt(saturate(1.0 - nx * nx - ny * ny)); r.normalTS = QD3D12_SafeNormalize(float3(nx, ny, nz), float3(0.0, 0.0, 1.0)); r.confidence = saturate(outv[5]) * evalFade; r.visibility = saturate(outv[6]) * evalFade; r.albedo = saturate(float3(outv[7], outv[8], outv[9])); r.specular = saturate(float3(outv[10], outv[11], outv[12])); r.active = 1.0; return r; } float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPOMResult nr) { if (nr.active > 0.5) return nr.uv; if (gUseNormalMap < 0.5) return baseUv; float confidence = QD3D12_GetPomConfidence(baseUv); if (confidence <= 0.03) return baseUv; float parallaxScale = gParallaxScale * confidence; if (abs(parallaxScale) < 1e-6) return baseUv; float3 n, t, b; QD3D12_BuildPixelTBN(i, n, t, b); // Correct view vector. The old code used -worldPos, which only works when // the camera is exactly at world origin and causes the obvious offset bugs // as soon as the camera moves. Keep -worldPos only as a reduced compatibility // fallback for older paths that cannot derive a perspective camera. float cameraConfidence = (gCameraPomValid >= 0.5) ? 1.0 : 0.35; float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos); float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n); float NoV = dot(n, viewWS); if (NoV <= 0.035) return baseUv; float3 viewTS = QD3D12_SafeNormalize(float3( dot(viewWS, t), dot(viewWS, b), NoV ), float3(0.0, 0.0, 1.0)); float ndotv = saturate(viewTS.z); float vz = max(ndotv, 0.22); // Fade out before far surfaces shimmer or when the viewing angle is too // grazing for estimated height maps. float viewDistance = (gCameraPomValid >= 0.5) ? max(length(gCameraWorldPos - i.worldPos), 1.0) : max(abs(i.currClip.w), 1.0); float distanceFade = 1.0 - smoothstep(QD3D12_POM_DISTANCE_NEAR, QD3D12_POM_DISTANCE_FAR, viewDistance); float grazingFade = smoothstep(0.08, 0.22, ndotv); float scale = parallaxScale * distanceFade * grazingFade * cameraConfidence; if (scale <= 0.00045) return baseUv; float layerCountF = lerp(10.0, 30.0, saturate(1.0 - ndotv)); layerCountF = lerp(8.0, layerCountF, saturate(distanceFade)); uint layerCount = (uint)clamp(layerCountF + 0.5, 8.0, 32.0); // Offset-limited POM. Divide by a softened z term and clamp the max UV walk; // this removes the extreme stretched-offset artifacts on steep angles. // Standard POM walks opposite the view vector in tangent space. The shift // cap is intentionally tight for generated RGB-only height, looser for real // authored alpha height. float2 parallaxVector = (viewTS.xy / vz) * scale; float parallaxLen = length(parallaxVector); float maxParallaxShift = lerp(0.024, 0.058, confidence); if (parallaxLen > maxParallaxShift && parallaxLen > 1e-6) parallaxVector *= maxParallaxShift / parallaxLen; float invLayerCount = rcp((float)layerCount); float2 deltaUv = parallaxVector * invLayerCount; float2 uv = baseUv; float2 prevUv = uv; float currentLayerDepth = 0.0; float prevLayerDepth = 0.0; float currentDepth = QD3D12_GetPomDepth(uv); float prevDepth = currentDepth; [loop] for (uint layer = 0u; layer < 32u; ++layer) { if (layer >= layerCount || currentLayerDepth >= currentDepth) break; prevUv = uv; prevLayerDepth = currentLayerDepth; prevDepth = currentDepth; uv -= deltaUv; currentLayerDepth += invLayerCount; currentDepth = QD3D12_GetPomDepth(uv); } float afterDepth = currentDepth - currentLayerDepth; float beforeDepth = prevDepth - prevLayerDepth; float denom = afterDepth - beforeDepth; float weight = (abs(denom) > 1e-5) ? saturate(afterDepth / denom) : 0.0; float2 refinedUv = lerp(uv, prevUv, weight); // Blend the final result in instead of applying a manual half-vector center // correction. The center correction was the source of several texture-offset // bugs on flat/low-confidence areas. return lerp(baseUv, refinedUv, saturate(distanceFade * grazingFade)); } float2 QD3D12_ComputeParallaxUV(VSOut i, float2 baseUv) { QD3D12NeuralPOMResult nr = QD3D12_EvaluateNeuralPOM(i, baseUv); return QD3D12_ComputeParallaxUVWithNeural(i, baseUv, nr); } float2 QD3D12_BuildMaterialUV0Cached(VSOut i, QD3D12NeuralPOMResult nr) { float2 uv0 = i.uv0; if (gUseNormalMap > 0.5 || gUseNeuralPOM > 0.5) uv0 = QD3D12_ComputeParallaxUVWithNeural(i, uv0, nr); // Do not dither POM UVs. Even tiny post-POM noise makes relief edges look // like a texture offset mismatch. Keep the old dither only for non-POM draws. if (gUseNormalMap <= 0.5 && gUseNeuralPOM <= 0.5) { float n = TinyNoise(int2(i.pos.xy)) * 0.0005; uv0 += float2(n, -n); } return uv0; } float2 QD3D12_BuildMaterialUV0(VSOut i) { QD3D12NeuralPOMResult nr = QD3D12_EvaluateNeuralPOM(i, i.uv0); return QD3D12_BuildMaterialUV0Cached(i, nr); } )HLSL" R"HLSL( float4 QD3D12_SampleGlowAtUV(VSOut i, float2 uv0) { if (gUseGlowMap <= 0.0) return float4(0.0, 0.0, 0.0, 0.0); float4 glow = gGlowMap.Sample(gSamp3, uv0); // 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 QD3D12_SampleGlow(VSOut i) { return QD3D12_SampleGlowAtUV(i, QD3D12_BuildMaterialUV0(i)); } )HLSL" R"HLSL( struct QD3D12MaterialEval { QD3D12NeuralPOMResult neural; float2 uv0; float4 glow; }; QD3D12MaterialEval QD3D12_BuildMaterialEval(VSOut i) { QD3D12MaterialEval m; m.neural = QD3D12_EvaluateNeuralPOM(i, i.uv0); m.uv0 = QD3D12_BuildMaterialUV0Cached(i, m.neural); m.glow = QD3D12_SampleGlowAtUV(i, m.uv0); return m; } float4 BuildTexturedColorCached(VSOut i, QD3D12MaterialEval m) { float4 primary = i.col; float4 outColor = primary; float2 uv0 = m.uv0; float2 uv1 = i.uv1; float n = TinyNoise(int2(i.pos.xy)) * 0.0005; uv1 += float2(-n, n); if (gUseTex0 > 0.5) { float4 tex0 = gTex0.Sample(gSamp0, uv0); // The neural payload is allowed to move the material UV, but the base color // must still come from the authored diffuse texture. Replacing tex0.rgb with // network albedo made a bad/incompatible payload turn the whole draw black. // Keep the learned albedo available only behind the debug toggle. if (gUseNeuralPOM > 0.5 && gNeuralPOMDebug > 0.5 && m.neural.active > 0.5) tex0.rgb = m.neural.albedo; // keep source alpha for alpha-test/cutout materials. outColor = ApplyTexCombine(outColor, tex0, primary, gTexEnvMode0, gTexComb0RGB, gTexComb0Alpha, gTexComb0Operand, gTexEnvColor0); } if (gUseTex1 > 0.5) { // Normal maps are bound through t2. Texture unit 1 remains a real fixed-function // color/lightmap stage when the CPU side marks it as usable, so do not drop // legacy lightmaps just because a normal map is active. float4 tex1 = gTex1.Sample(gSamp1, uv1); outColor = ApplyTexCombine(outColor, tex1, primary, gTexEnvMode1, gTexComb1RGB, gTexComb1Alpha, gTexComb1Operand, gTexEnvColor1); } if (gUseGlowMap > 0.0) { float glowMask = saturate(m.glow.a); outColor.rgb *= (1.0 - glowMask); } // outColor.xyz = ApplySoftwareRendererLook(outColor.xyz); outColor = ApplyFog(outColor, i.fogCoord); return outColor; } float4 BuildTexturedColor(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); return BuildTexturedColorCached(i, m); } float4 BuildGlowEmissionCached(QD3D12MaterialEval m) { return m.glow; } float4 BuildGlowEmission(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); return BuildGlowEmissionCached(m); } )HLSL" R"HLSL( float4 BuildSpecularAlbedoCached(VSOut i, QD3D12MaterialEval m) { if (gUseSpecularMap > 0.0) { float4 spec = gSpecularMap.Sample(gSamp4, m.uv0); float strength = max(gSpecularMapStrength, 0.0); // RGB-only legacy spec maps are uploaded with forced opaque alpha. For real // RGBA spec maps, alpha is treated as an artist mask over RGB. The alpha we // write to the G-buffer is not the brightness mask; it is a presence bit so // the DXR pass can distinguish "no spec map bound" from an intentional // black spec-map texel. bool alphaLooksForcedOpaque = (spec.a >= 0.999); float alphaMask = alphaLooksForcedOpaque ? 1.0 : saturate(spec.a); float3 specRgb = saturate(spec.rgb) * alphaMask * strength; return float4(specRgb, 1.0); } // Same rule as diffuse: do not let the neural output silently replace authored // material data unless explicitly debugging the neural payload. if (gUseNeuralPOM > 0.5 && gNeuralPOMDebug > 0.5 && m.neural.active > 0.5) { float3 specRgb = saturate(m.neural.specular); float present = (max(max(specRgb.r, specRgb.g), specRgb.b) > 0.001) ? 1.0 : 0.0; return float4(specRgb, present); } return float4(0.0, 0.0, 0.0, 0.0); } float4 BuildSpecularAlbedo(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); return BuildSpecularAlbedoCached(i, m); } float3 BuildGBufferNormalCached(VSOut i, QD3D12MaterialEval m) { float3 n = QD3D12_SafeNormalize(i.normal, float3(0.0, 0.0, 1.0)); if (gUseNormalMap < 0.5 && (gUseNeuralPOM < 0.5 || m.neural.active <= 0.5)) return n; if (gUseNeuralPOM > 0.5 && m.neural.active > 0.5) { float3 neuralN = QD3D12_SafeNormalize(m.neural.basisN, n); float3 neuralT = QD3D12_SafeNormalize(m.neural.basisT, QD3D12_BuildFallbackTangent(neuralN)); float3 neuralB = QD3D12_SafeNormalize(m.neural.basisB, QD3D12_SafeNormalize(cross(neuralN, neuralT), float3(0.0, 1.0, 0.0))); float3 neuralNormalTS = m.neural.normalTS; return QD3D12_SafeNormalize( neuralNormalTS.x * neuralT + neuralNormalTS.y * neuralB + neuralNormalTS.z * neuralN, neuralN); } if (gUseNormalMap < 0.5) return n; float3 t, b; QD3D12_BuildPixelTBN(i, n, t, b); float3 tangentNormal = QD3D12_DecodeTangentSpaceNormal(gNormalMap.Sample(gSamp2, m.uv0).xyz); return QD3D12_SafeNormalize( tangentNormal.x * t + tangentNormal.y * b + tangentNormal.z * n, n); } float3 BuildGBufferNormal(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); return BuildGBufferNormalCached(i, m); } float2 ClipToUv(float4 clipPos) { float2 ndc = clipPos.xy / max(abs(clipPos.w), 1e-6); return float2(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5); } float4 BuildVelocity(VSOut i) { float2 currUv = ClipToUv(i.currClip); float2 prevUv = ClipToUv(i.prevClip); currUv -= gJitterPixels * gInvRenderSize; prevUv -= gPrevJitterPixels * gInvRenderSize; float currDepth = i.currClip.z / max(abs(i.currClip.w), 1e-6); float prevDepth = i.prevClip.z / max(abs(i.prevClip.w), 1e-6); return float4(prevUv - currUv, prevDepth - currDepth, gMaterialType); } VSOut VSMain(VSIn i) { VSOut o; float4 worldPos = mul(gModelMatrix, float4(i.pos, 1.0)); float4 currClip = mul(gMVP, float4(i.pos, 1.0)); float4 prevClip = mul(gPrevMVP, float4(i.pos, 1.0)); float3 worldNormal = mul((float3x3)gModelMatrix, i.normal); float3 worldTangent = mul((float3x3)gModelMatrix, i.tangent); float3 worldBinormal = mul((float3x3)gModelMatrix, i.binormal); currClip.z = 0.5 * (currClip.z + currClip.w); o.pos = currClip; o.currClip = currClip; o.prevClip = prevClip; o.objPos = i.pos; o.objNormal = i.normal; o.fogCoord = (gCameraPomValid > 0.5) ? length(worldPos.xyz - gCameraWorldPos) : abs(currClip.z / max(abs(currClip.w), 0.00001)); o.uv0 = i.uv0; o.uv1 = i.uv1; o.col = i.col; o.worldPos = worldPos.xyz; o.normal = normalize(worldNormal); o.tangent = normalize(worldTangent); o.binormal = normalize(worldBinormal); o.attr = float4(geometryFlag, gRoughness, gMaterialType, 0.0); o.psize = gPointSize; return o; } )HLSL" R"HLSL( struct TessCP { float4 pos : POSITION0; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 col : COLOR0; float fogCoord : TEXCOORD2; float3 worldPos : TEXCOORD3; float3 normal : TEXCOORD4; float3 tangent : TEXCOORD5; float3 binormal : TEXCOORD6; float4 attr : TEXCOORD7; float4 prevClip : TEXCOORD8; float4 currClip : TEXCOORD9; float3 objPos : TEXCOORD10; float3 objNormal : TEXCOORD11; }; struct HSConstOut { float edge[3] : SV_TessFactor; float inside : SV_InsideTessFactor; }; TessCP QD3D12_MakeTessCP(VSOut i) { TessCP o; o.pos = i.pos; o.uv0 = i.uv0; o.uv1 = i.uv1; o.col = i.col; o.fogCoord = i.fogCoord; o.worldPos = i.worldPos; o.normal = i.normal; o.tangent = i.tangent; o.binormal = i.binormal; o.attr = i.attr; o.prevClip = i.prevClip; o.currClip = i.currClip; o.objPos = i.objPos; o.objNormal = i.objNormal; return o; } float2 QD3D12_ClipXYToNdc(float4 clipPos) { return clipPos.xy / max(abs(clipPos.w), 1.0e-6); } float QD3D12_EdgeLengthPixels(float4 clipA, float4 clipB) { float2 ndcA = QD3D12_ClipXYToNdc(clipA); float2 ndcB = QD3D12_ClipXYToNdc(clipB); return length((ndcA - ndcB) * max(gRenderSize, float2(1.0, 1.0)) * 0.5); } float QD3D12_ViewDistanceFromClip(float4 clipPos) { // Clip-space w is proportional to view distance for the perspective camera // path this tessellation route is restricted to. return max(abs(clipPos.w), 1.0); } float QD3D12_DistanceFadeFromClip(float4 clipPos, float nearDistance, float farDistance) { return 1.0 - smoothstep(nearDistance, farDistance, QD3D12_ViewDistanceFromClip(clipPos)); } float QD3D12_TessellationEdgeDistanceFade(VSOut a, VSOut b) { // Edge-only distance keeps neighboring triangles crack-free: both triangles // sharing an edge use the same two control points to compute the fade. float edgeDistance = 0.5 * (QD3D12_ViewDistanceFromClip(a.currClip) + QD3D12_ViewDistanceFromClip(b.currClip)); return 1.0 - smoothstep(QD3D12_TESS_DISTANCE_NEAR, QD3D12_TESS_DISTANCE_FAR, edgeDistance); } float QD3D12_TessellationDisplacementFade(float4 clipPos) { return QD3D12_DistanceFadeFromClip(clipPos, QD3D12_TESS_DISTANCE_NEAR, QD3D12_TESS_DISTANCE_FAR); } float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b) { if (gUseNormalMap <= 0.5) return 1.0; float distanceFade = QD3D12_TessellationEdgeDistanceFade(a, b); if (distanceFade <= 0.001) return 1.0; float ha = QD3D12_GetFilteredTessHeight(a.uv0); float hb = QD3D12_GetFilteredTessHeight(b.uv0); float hm = QD3D12_GetFilteredTessHeight((a.uv0 + b.uv0) * 0.5); // This edge-only factor is the crack fix: neighboring triangles that share // this edge compute the same tessellation factor because they use only the // two shared vertices and the same midpoint sample. float heightRange = max(abs(ha - hb), abs(hm - (ha + hb) * 0.5) * 2.0); // Do not subdivide flat/low-relief normal maps just because a normal map is // bound. This is the matching geometry-side fix for the flat-area POM gate. float edgeRelief = saturate((heightRange - 0.018) / 0.16); edgeRelief = edgeRelief * edgeRelief * (3.0 - 2.0 * edgeRelief); if (edgeRelief <= 0.001) return 1.0; float edgePixels = QD3D12_EdgeLengthPixels(a.currClip, b.currClip); float uvSpan = length(a.uv0 - b.uv0); float strength = clamp(max(gNormalMapStrength, 0.0), 0.0, 4.0); float screenTerm = sqrt(max(edgePixels, 1.0) / QD3D12_TESS_TARGET_EDGE_PIXELS); float heightTerm = heightRange * (10.0 + strength * 4.0); float uvTerm = saturate(uvSpan * 4.0) * 2.0; float displacementTerm = saturate(gTessellationDisplacement * 6.0) * 2.0; float nearFactor = 1.0 + edgeRelief * (strength * 0.75 + screenTerm * 3.0 + heightTerm + uvTerm + displacementTerm); nearFactor = clamp(nearFactor, QD3D12_TESS_NEAR_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR); // Fade all subdivision pressure toward 1.0 with distance. Near surfaces keep // the detailed factor; far surfaces become ordinary triangles. float factor = lerp(1.0, nearFactor, distanceFade); return clamp(factor, QD3D12_TESS_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR); } HSConstOut HSMainConstants(InputPatch patch) { HSConstOut o; if (gUseNormalMap <= 0.5) { o.edge[0] = 1.0; o.edge[1] = 1.0; o.edge[2] = 1.0; o.inside = 1.0; return o; } // D3D tri patch edge order is opposite the named control point: // edge 0 = CP1-CP2, edge 1 = CP2-CP0, edge 2 = CP0-CP1. float e0 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[1], patch[2]); float e1 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[2], patch[0]); float e2 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[0], patch[1]); float maxEdge = max(e0, max(e1, e2)); o.edge[0] = e0; o.edge[1] = e1; o.edge[2] = e2; o.inside = clamp((e0 + e1 + e2 + maxEdge) * 0.25, QD3D12_TESS_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR); return o; } [domain("tri")] [partitioning("fractional_odd")] [outputtopology("triangle_ccw")] [outputcontrolpoints(3)] [patchconstantfunc("HSMainConstants")] [maxtessfactor(15.0)] TessCP HSMain(InputPatch patch, uint cpId : SV_OutputControlPointID) { return QD3D12_MakeTessCP(patch[cpId]); } float2 QD3D12_Interp2(float2 a, float2 b, float2 c, float3 w) { return a * w.x + b * w.y + c * w.z; } float3 QD3D12_Interp3(float3 a, float3 b, float3 c, float3 w) { return a * w.x + b * w.y + c * w.z; } float4 QD3D12_Interp4(float4 a, float4 b, float4 c, float3 w) { return a * w.x + b * w.y + c * w.z; } [domain("tri")] VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const OutputPatch patch) { TessCP i; i.pos = QD3D12_Interp4(patch[0].pos, patch[1].pos, patch[2].pos, bary); i.uv0 = QD3D12_Interp2(patch[0].uv0, patch[1].uv0, patch[2].uv0, bary); i.uv1 = QD3D12_Interp2(patch[0].uv1, patch[1].uv1, patch[2].uv1, bary); i.col = QD3D12_Interp4(patch[0].col, patch[1].col, patch[2].col, bary); i.fogCoord = patch[0].fogCoord * bary.x + patch[1].fogCoord * bary.y + patch[2].fogCoord * bary.z; i.worldPos = QD3D12_Interp3(patch[0].worldPos, patch[1].worldPos, patch[2].worldPos, bary); i.normal = QD3D12_Interp3(patch[0].normal, patch[1].normal, patch[2].normal, bary); i.tangent = QD3D12_Interp3(patch[0].tangent, patch[1].tangent, patch[2].tangent, bary); i.binormal = QD3D12_Interp3(patch[0].binormal, patch[1].binormal, patch[2].binormal, bary); i.attr = QD3D12_Interp4(patch[0].attr, patch[1].attr, patch[2].attr, bary); i.prevClip = QD3D12_Interp4(patch[0].prevClip, patch[1].prevClip, patch[2].prevClip, bary); i.currClip = QD3D12_Interp4(patch[0].currClip, patch[1].currClip, patch[2].currClip, bary); i.objPos = QD3D12_Interp3(patch[0].objPos, patch[1].objPos, patch[2].objPos, bary); i.objNormal = QD3D12_Interp3(patch[0].objNormal, patch[1].objNormal, patch[2].objNormal, bary); VSOut o; float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0)); float height = QD3D12_GetFilteredTessHeight(i.uv0); float centeredHeight = QD3D12_CleanCenteredTessHeight(height); float displacementFade = QD3D12_TessellationDisplacementFade(i.currClip); float reliefConfidence = QD3D12_GetPomConfidence(i.uv0); float displacement = centeredHeight * gTessellationDisplacement * displacementFade * reliefConfidence; float3 displacedObjPos = i.objPos + objNormal * displacement; float4 worldPos = mul(gModelMatrix, float4(displacedObjPos, 1.0)); float4 currClip = mul(gMVP, float4(displacedObjPos, 1.0)); float4 prevClip = mul(gPrevMVP, float4(displacedObjPos, 1.0)); float3 worldNormal = mul((float3x3)gModelMatrix, objNormal); currClip.z = 0.5 * (currClip.z + currClip.w); o.pos = currClip; o.currClip = currClip; o.prevClip = prevClip; o.objPos = displacedObjPos; o.objNormal = objNormal; o.fogCoord = (gCameraPomValid > 0.5) ? length(worldPos.xyz - gCameraWorldPos) : abs(currClip.z / max(abs(currClip.w), 0.00001)); o.uv0 = i.uv0; o.uv1 = i.uv1; o.col = i.col; o.worldPos = worldPos.xyz; o.normal = QD3D12_SafeNormalize(worldNormal, QD3D12_SafeNormalize(i.normal, float3(0.0, 0.0, 1.0))); o.tangent = QD3D12_SafeNormalize(i.tangent, QD3D12_BuildFallbackTangent(o.normal)); o.binormal = QD3D12_SafeNormalize(i.binormal, QD3D12_SafeNormalize(cross(o.normal, o.tangent), float3(0.0, 1.0, 0.0))); o.attr = float4(geometryFlag, gRoughness, gMaterialType, 0.0); o.psize = gPointSize; return o; } PSOut PSMain(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); PSOut o; o.color = BuildTexturedColorCached(i, m); if (gAlphaBlendPass > 0.5) o.color.rgb += BuildGlowEmissionCached(m).rgb; o.normal = float4(BuildGBufferNormalCached(i, m), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); o.emissive = BuildGlowEmissionCached(m); o.specular = BuildSpecularAlbedoCached(i, m); return o; } PSOut PSMainAlphaTest(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); PSOut o; o.color = BuildTexturedColorCached(i, m); QD3D12_AlphaTest(o.color.a); o.normal = float4(BuildGBufferNormalCached(i, m), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); o.emissive = BuildGlowEmissionCached(m); o.specular = BuildSpecularAlbedoCached(i, m); return o; } PSOut PSMainUntextured(VSOut i) { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); PSOut o; o.color = ApplyFog(i.col, i.fogCoord); o.normal = float4(BuildGBufferNormalCached(i, m), 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); o.specular = BuildSpecularAlbedoCached(i, m); return o; } float4 PSMainColorOnly(VSOut i) : SV_Target0 { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); float4 c = BuildTexturedColorCached(i, m); c.rgb += BuildGlowEmissionCached(m).rgb; return c; } float4 PSMainAlphaTestColorOnly(VSOut i) : SV_Target0 { QD3D12MaterialEval m = QD3D12_BuildMaterialEval(i); float4 c = BuildTexturedColorCached(i, m); QD3D12_AlphaTest(c.a); c.rgb += BuildGlowEmissionCached(m).rgb; return c; } )HLSL" R"HLSL( float4 PSMainUntexturedColorOnly(VSOut i) : SV_Target0 { return ApplyFog(i.col, i.fogCoord); } )HLSL"; static const char* kQD3D12PostHLSL = R"HLSL( Texture2D gTex0 : register(t0); Texture2DMS gDepthMS : register(t1); Texture2DMS gNormalMS : register(t2); Texture2DMS gPositionMS : register(t3); Texture2DMS gVelocityMS : register(t4); Texture2DMS gEmissiveMS : register(t5); Texture2DMS gSpecularMS : register(t6); Texture2D gTaaHistory : register(t7); Texture2D gTaaVelocity : register(t8); Texture2D gFogPosition : register(t9); Texture2D gFogDepth : register(t10); SamplerState gSamp0 : register(s0); cbuffer PostCB : register(b0) { // gPostParams0: x = sharpen strength, y = history weight, z = TAA reset flag. // gPostParams1: x = tone-map exposure, y = white-point scale, z = display brightness. // gPostParams2: x = fog enabled, y = fog mode, z = fog density, w = fog start. // gPostParams3: x = fog end, y = fog camera valid. float4 gPostParams0; float4 gPostParams1; float4 gPostParams2; float4 gPostParams3; float4 gPostFogColor; float4 gPostFogCameraWorldPos; }; #define gPostSharpness gPostParams0.x #define gTaaHistoryWeight gPostParams0.y #define gTaaReset gPostParams0.z #define gToneMapExposure gPostParams1.x #define gToneMapWhiteScale gPostParams1.y #define gToneMapBrightness gPostParams1.z #define gPostFogEnabled gPostParams2.x #define gPostFogMode gPostParams2.y #define gPostFogDensity gPostParams2.z #define gPostFogStart gPostParams2.w #define gPostFogEnd gPostParams3.x #define gPostFogCameraValid gPostParams3.y #define QD3D12_TONEMAP_TILE_DIM 64u struct VSOut { float4 pos : SV_Position; float2 uv : TEXCOORD0; }; VSOut VSMain(uint vid : SV_VertexID) { VSOut o; float2 pos; if (vid == 0) pos = float2(-1.0, -1.0); else if (vid == 1) pos = float2(-1.0, 3.0); else pos = float2(3.0, -1.0); o.pos = float4(pos, 0.0, 1.0); o.uv = float2(pos.x * 0.5 + 0.5, -pos.y * 0.5 + 0.5); return o; } float QD3D12_PostFogFactor(float fogCoord) { if (gPostFogEnabled < 0.5) return 1.0; float f; if (gPostFogMode < 0.5) { float denom = max(gPostFogEnd - gPostFogStart, 0.00001); f = (gPostFogEnd - fogCoord) / denom; } else if (gPostFogMode < 1.5) { f = exp(-gPostFogDensity * fogCoord); } else { float d = gPostFogDensity * fogCoord; f = exp(-(d * d)); } return saturate(f); } float4 QD3D12_ApplyPostFog(float4 color, float2 uv) { if (gPostFogEnabled < 0.5 || gPostFogCameraValid < 0.5) return color; float sceneDepth = gFogDepth.SampleLevel(gSamp0, uv, 0.0); if (sceneDepth >= 0.99999) return color; float3 worldPos = gFogPosition.SampleLevel(gSamp0, uv, 0.0).xyz; float fogCoord = length(worldPos - gPostFogCameraWorldPos.xyz); float fogFactor = QD3D12_PostFogFactor(fogCoord); float3 fogged = lerp(gPostFogColor.rgb, color.rgb, fogFactor); color.rgb = lerp(color.rgb, fogged, saturate(gPostFogColor.a)); return color; } float4 PSCopy(VSOut i) : SV_Target0 { return QD3D12_ApplyPostFog(gTex0.Sample(gSamp0, i.uv), i.uv); } float4 PSSharpen(VSOut i) : SV_Target0 { float4 center = gTex0.Sample(gSamp0, i.uv); float amount = saturate(gPostSharpness); if (amount <= 0.0001) return center; uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); float3 n = gTex0.Sample(gSamp0, saturate(i.uv + float2(0.0, -texel.y))).rgb; float3 s = gTex0.Sample(gSamp0, saturate(i.uv + float2(0.0, texel.y))).rgb; float3 e = gTex0.Sample(gSamp0, saturate(i.uv + float2( texel.x, 0.0))).rgb; float3 w = gTex0.Sample(gSamp0, saturate(i.uv + float2(-texel.x, 0.0))).rgb; float3 blur = (n + s + e + w) * 0.25; // Sharpen strictly after temporal reconstruction. Keep it conservative so // the TAA resolve is not undone by ringing on high-contrast UI/geometry edges. float3 sharp = center.rgb + (center.rgb - blur) * amount; return QD3D12_ApplyPostFog(float4(saturate(sharp), center.a), i.uv); } float QD3D12_ToneMapBrightness(float3 rgb) { rgb = max(rgb, 0.0); // Use max channel instead of weighted luma so saturated colored lights still // count as a bright pixel for white-point selection. return max(max(rgb.r, rgb.g), rgb.b); } float4 PSToneMapTileMax(VSOut i) : SV_Target0 { uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); if (srcW == 0u || srcH == 0u) return float4(1.0, 0.0, 0.0, 1.0); uint2 tile = min( uint2((uint)i.pos.x, (uint)i.pos.y), uint2(QD3D12_TONEMAP_TILE_DIM - 1u, QD3D12_TONEMAP_TILE_DIM - 1u)); uint x0 = (tile.x * srcW) / QD3D12_TONEMAP_TILE_DIM; uint y0 = (tile.y * srcH) / QD3D12_TONEMAP_TILE_DIM; uint x1 = ((tile.x + 1u) * srcW + QD3D12_TONEMAP_TILE_DIM - 1u) / QD3D12_TONEMAP_TILE_DIM; uint y1 = ((tile.y + 1u) * srcH + QD3D12_TONEMAP_TILE_DIM - 1u) / QD3D12_TONEMAP_TILE_DIM; x0 = min(x0, srcW - 1u); y0 = min(y0, srcH - 1u); x1 = min(max(x1, x0 + 1u), srcW); y1 = min(max(y1, y0 + 1u), srcH); float maxBrightness = 0.0; [loop] for (uint y = y0; y < y1; ++y) { [loop] for (uint x = x0; x < x1; ++x) { float3 hdr = gTex0.Load(int3(int2(x, y), 0)).rgb; maxBrightness = max(maxBrightness, QD3D12_ToneMapBrightness(hdr)); } } return float4(min(maxBrightness, 65504.0), 0.0, 0.0, 1.0); } float4 PSToneMapFinalMax(VSOut i) : SV_Target0 { uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); float maxBrightness = 1.0; [loop] for (uint y = 0u; y < srcH; ++y) { [loop] for (uint x = 0u; x < srcW; ++x) { maxBrightness = max(maxBrightness, gTex0.Load(int3(int2(x, y), 0)).r); } } return float4(min(maxBrightness, 65504.0), 0.0, 0.0, 1.0); } float3 QD3D12_PostSharpenHDR(float2 uv, float3 center) { float amount = saturate(gPostSharpness); if (amount <= 0.0001) return center; uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); float3 n = max(gTex0.Sample(gSamp0, saturate(uv + float2(0.0, -texel.y))).rgb, 0.0); float3 s = max(gTex0.Sample(gSamp0, saturate(uv + float2(0.0, texel.y))).rgb, 0.0); float3 e = max(gTex0.Sample(gSamp0, saturate(uv + float2( texel.x, 0.0))).rgb, 0.0); float3 w = max(gTex0.Sample(gSamp0, saturate(uv + float2(-texel.x, 0.0))).rgb, 0.0); float3 blur = (n + s + e + w) * 0.25; // Sharpen before tone mapping so highlights keep their shoulder instead of // ringing after they have already been compressed to display range. return min(max(center + (center - blur) * amount, 0.0), 65504.0); } float3 QD3D12_ExtendedReinhard(float3 hdr, float whitePoint) { hdr = max(hdr * max(gToneMapExposure, 0.0), 0.0); // Use the reduced brightest pixel as the white point. The white point is // clamped to at least 1 so the pass never auto-brightens a dark/LDR frame. float w = max(whitePoint * max(gToneMapWhiteScale, 0.001), 1.0); float w2 = max(w * w, 1.0e-4); float3 mapped = (hdr * (1.0 + hdr / w2)) / (1.0 + hdr); return saturate(mapped * max(gToneMapBrightness, 0.0)); } float4 PSToneMap(VSOut i) : SV_Target0 { float4 center = gTex0.Sample(gSamp0, i.uv); // Root table t7 is the 1x1 brightest-pixel reduction for this shader. It is // also used as TAA history by PSTAA, but those entries are never active in the // same PSO. float whitePoint = max(gTaaHistory.SampleLevel(gSamp0, float2(0.5, 0.5), 0.0).r, 1.0); float3 hdr = QD3D12_PostSharpenHDR(i.uv, max(center.rgb, 0.0)); float3 mapped = QD3D12_ExtendedReinhard(hdr, whitePoint); // Do not apply a gamma pow here. This shim's swap chain and scene buffers are // UNORM, not sRGB formats, and the legacy renderer already authored/displayed // color in that space. A gamma conversion here is what made the prior patch // look blown out. return QD3D12_ApplyPostFog(float4(mapped, saturate(center.a)), i.uv); } float4 PSTAA(VSOut i) : SV_Target0 { float4 current = gTex0.Sample(gSamp0, i.uv); uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); float2 velocity = gTaaVelocity.Sample(gSamp0, i.uv).xy; // previousUV - currentUV float2 prevUv = i.uv + velocity; bool historyValid = gTaaReset < 0.5 && prevUv.x >= 0.0 && prevUv.x <= 1.0 && prevUv.y >= 0.0 && prevUv.y <= 1.0; if (!historyValid) return current; float4 history = gTaaHistory.Sample(gSamp0, prevUv); float3 minColor = current.rgb; float3 maxColor = current.rgb; [unroll] for (int oy = -1; oy <= 1; ++oy) { [unroll] for (int ox = -1; ox <= 1; ++ox) { float3 tap = gTex0.Sample(gSamp0, saturate(i.uv + float2((float)ox, (float)oy) * texel)).rgb; minColor = min(minColor, tap); maxColor = max(maxColor, tap); } } // Clip history to the current neighborhood to avoid ghost trails. history.rgb = clamp(history.rgb, minColor - 0.015, maxColor + 0.015); float speed = saturate(length(velocity) * max(float(srcW), float(srcH)) * 0.075); float historyWeight = saturate(gTaaHistoryWeight) * (1.0 - speed * 0.65); float4 resolved = lerp(current, history, historyWeight); resolved.a = current.a; return resolved; } 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 { 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 { uint srcW, srcH; gTex0.GetDimensions(srcW, srcH); float2 uv = saturate(i.uv); uint2 srcPixel = min( (uint2)(uv * float2(srcW, srcH)), uint2(max(srcW, 1u) - 1u, max(srcH, 1u) - 1u)); return gTex0.Load(int3(int2(srcPixel), 0)).r; } uint QD3D12_SelectNearestDepthSample(uint2 srcPixel, uint sampleCount) { uint nearestSample = 0u; float nearestDepth = 1.0; [loop] for (uint sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) { float sampleDepth = gDepthMS.Load(int2(srcPixel), sampleIndex); if (sampleDepth < nearestDepth) { nearestDepth = sampleDepth; nearestSample = sampleIndex; } } return nearestSample; } float PSDepthResolveMS(VSOut i) : SV_Depth { uint srcW, srcH, sampleCount; gDepthMS.GetDimensions(srcW, srcH, sampleCount); float2 uv = saturate(i.uv); uint2 srcPixel = min( (uint2)(uv * float2(srcW, srcH)), uint2(max(srcW, 1u) - 1u, max(srcH, 1u) - 1u)); float depth = 1.0; [loop] for (uint sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) depth = min(depth, gDepthMS.Load(int2(srcPixel), sampleIndex)); return depth; } struct PSGBufferPointResolveOut { float4 normal : SV_Target0; float4 position : SV_Target1; float4 velocity : SV_Target2; float4 emissive : SV_Target3; float4 specular : SV_Target4; }; PSGBufferPointResolveOut PSGBufferPointResolveMS(VSOut i) { uint srcW, srcH, sampleCount; gDepthMS.GetDimensions(srcW, srcH, sampleCount); float2 uv = saturate(i.uv); uint2 srcPixel = min( (uint2)(uv * float2(srcW, srcH)), uint2(max(srcW, 1u) - 1u, max(srcH, 1u) - 1u)); uint sampleIndex = QD3D12_SelectNearestDepthSample(srcPixel, sampleCount); PSGBufferPointResolveOut o; // Do not hardware-resolve these buffers. Normals, positions, material flags, // and motion vectors are discontinuous at geometry edges, so averaging them // corrupts the deferred/DXR inputs. Pick one MSAA sample by depth instead. 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); o.specular = gSpecularMS.Load(int2(srcPixel), sampleIndex); return o; } )HLSL"; static size_t QD3D12_TypeSize(GLenum type) { switch (type) { case GL_BYTE: return sizeof(GLbyte); case GL_UNSIGNED_BYTE: return sizeof(GLubyte); case GL_SHORT: return sizeof(GLshort); case GL_UNSIGNED_SHORT: return sizeof(GLushort); case GL_INT: return sizeof(GLint); case GL_UNSIGNED_INT: return sizeof(GLuint); case GL_FLOAT: return sizeof(GLfloat); case GL_DOUBLE: return sizeof(GLdouble); default: return 0; } } static inline float QD3D12_ReadScalarFast(const uint8_t* p, GLenum type) { switch (type) { case GL_FLOAT: return *(const float*)p; case GL_DOUBLE: return (float)(*(const double*)p); case GL_INT: return (float)(*(const GLint*)p); case GL_UNSIGNED_INT: return (float)(*(const GLuint*)p); case GL_SHORT: return (float)(*(const GLshort*)p); case GL_UNSIGNED_SHORT: return (float)(*(const GLushort*)p); case GL_BYTE: return (float)(*(const GLbyte*)p); case GL_UNSIGNED_BYTE: return (float)(*(const GLubyte*)p); default: return 0.0f; } } static void QD3D12_FetchArrayVertex(GLint idx, GLVertex& out) { // Direct init is much cheaper than memset + patching fields. out.px = 0.0f; out.py = 0.0f; out.pz = 0.0f; out.nx = g_gl.curNormal[0]; out.ny = g_gl.curNormal[1]; out.nz = g_gl.curNormal[2]; out.r = 1.0f; out.g = 1.0f; out.b = 1.0f; out.a = 1.0f; out.u0 = 0.0f; out.v0 = 0.0f; out.u1 = 0.0f; out.v1 = 0.0f; out.tx = g_gl.curTangent[0]; out.ty = g_gl.curTangent[1]; out.tz = g_gl.curTangent[2]; out.bx = g_gl.curBinormal[0]; out.by = g_gl.curBinormal[1]; out.bz = g_gl.curBinormal[2]; // // Position // const auto& va = g_gl.vertexArray; if (va.enabled && va.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(va.type); const size_t elemSize = (size_t)va.size * typeSize; const size_t stride = va.stride ? (size_t)va.stride : elemSize; const uint8_t* p = va.ptr + stride * (size_t)idx; switch (va.type) { case GL_FLOAT: { const float* f = (const float*)p; if (va.size > 0) out.px = f[0]; if (va.size > 1) out.py = f[1]; if (va.size > 2) out.pz = f[2]; break; } case GL_DOUBLE: { const double* f = (const double*)p; if (va.size > 0) out.px = (float)f[0]; if (va.size > 1) out.py = (float)f[1]; if (va.size > 2) out.pz = (float)f[2]; break; } default: if (va.size > 0) out.px = QD3D12_ReadScalarFast(p + 0 * typeSize, va.type); if (va.size > 1) out.py = QD3D12_ReadScalarFast(p + 1 * typeSize, va.type); if (va.size > 2) out.pz = QD3D12_ReadScalarFast(p + 2 * typeSize, va.type); break; } } // // Normal // const auto& na = g_gl.normalArray; if (na.enabled && na.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(na.type); const size_t stride = na.stride ? (size_t)na.stride : (3 * typeSize); const uint8_t* p = na.ptr + stride * (size_t)idx; switch (na.type) { case GL_FLOAT: { const float* f = (const float*)p; out.nx = f[0]; out.ny = f[1]; out.nz = f[2]; break; } case GL_DOUBLE: { const double* f = (const double*)p; out.nx = (float)f[0]; out.ny = (float)f[1]; out.nz = (float)f[2]; break; } default: out.nx = QD3D12_ReadScalarFast(p + 0 * typeSize, na.type); out.ny = QD3D12_ReadScalarFast(p + 1 * typeSize, na.type); out.nz = QD3D12_ReadScalarFast(p + 2 * typeSize, na.type); break; } } // // Tangent // const auto& ta = g_gl.tangentArray; if (ta.enabled && ta.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(ta.type); const size_t stride = ta.stride ? (size_t)ta.stride : (3 * typeSize); const uint8_t* p = ta.ptr + stride * (size_t)idx; switch (ta.type) { case GL_FLOAT: { const float* f = (const float*)p; out.tx = f[0]; out.ty = f[1]; out.tz = f[2]; break; } case GL_DOUBLE: { const double* f = (const double*)p; out.tx = (float)f[0]; out.ty = (float)f[1]; out.tz = (float)f[2]; break; } default: out.tx = QD3D12_ReadScalarFast(p + 0 * typeSize, ta.type); out.ty = QD3D12_ReadScalarFast(p + 1 * typeSize, ta.type); out.tz = QD3D12_ReadScalarFast(p + 2 * typeSize, ta.type); break; } } // // Bitangent // const auto& ba = g_gl.bitangentArray; if (ba.enabled && ba.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(ba.type); const size_t stride = ba.stride ? (size_t)ba.stride : (3 * typeSize); const uint8_t* p = ba.ptr + stride * (size_t)idx; switch (ba.type) { case GL_FLOAT: { const float* f = (const float*)p; out.bx = f[0]; out.by = f[1]; out.bz = f[2]; break; } case GL_DOUBLE: { const double* f = (const double*)p; out.bx = (float)f[0]; out.by = (float)f[1]; out.bz = (float)f[2]; break; } default: out.bx = QD3D12_ReadScalarFast(p + 0 * typeSize, ba.type); out.by = QD3D12_ReadScalarFast(p + 1 * typeSize, ba.type); out.bz = QD3D12_ReadScalarFast(p + 2 * typeSize, ba.type); break; } } // // Color // const auto& ca = g_gl.colorArray; if (ca.enabled && ca.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(ca.type); const size_t elemSize = (size_t)ca.size * typeSize; const size_t stride = ca.stride ? (size_t)ca.stride : elemSize; const uint8_t* p = ca.ptr + stride * (size_t)idx; if (ca.type == GL_UNSIGNED_BYTE) { static const float kInv255 = 1.0f / 255.0f; if (ca.size > 0) out.r = p[0] * kInv255; if (ca.size > 1) out.g = p[1] * kInv255; if (ca.size > 2) out.b = p[2] * kInv255; if (ca.size > 3) out.a = p[3] * kInv255; } else if (ca.type == GL_FLOAT) { const float* f = (const float*)p; if (ca.size > 0) out.r = f[0]; if (ca.size > 1) out.g = f[1]; if (ca.size > 2) out.b = f[2]; if (ca.size > 3) out.a = f[3]; } else if (ca.type == GL_DOUBLE) { const double* f = (const double*)p; if (ca.size > 0) out.r = (float)f[0]; if (ca.size > 1) out.g = (float)f[1]; if (ca.size > 2) out.b = (float)f[2]; if (ca.size > 3) out.a = (float)f[3]; } else { if (ca.size > 0) out.r = QD3D12_ReadScalarFast(p + 0 * typeSize, ca.type); if (ca.size > 1) out.g = QD3D12_ReadScalarFast(p + 1 * typeSize, ca.type); if (ca.size > 2) out.b = QD3D12_ReadScalarFast(p + 2 * typeSize, ca.type); if (ca.size > 3) out.a = QD3D12_ReadScalarFast(p + 3 * typeSize, ca.type); } } // // Texcoord 0 // { const auto& tc = g_gl.texCoordArray[0]; if (tc.enabled && tc.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(tc.type); const size_t elemSize = (size_t)tc.size * typeSize; const size_t stride = tc.stride ? (size_t)tc.stride : elemSize; const uint8_t* p = tc.ptr + stride * (size_t)idx; switch (tc.type) { case GL_FLOAT: { const float* f = (const float*)p; if (tc.size > 0) out.u0 = f[0]; if (tc.size > 1) out.v0 = f[1]; break; } case GL_DOUBLE: { const double* f = (const double*)p; if (tc.size > 0) out.u0 = (float)f[0]; if (tc.size > 1) out.v0 = (float)f[1]; break; } default: if (tc.size > 0) out.u0 = QD3D12_ReadScalarFast(p + 0 * typeSize, tc.type); if (tc.size > 1) out.v0 = QD3D12_ReadScalarFast(p + 1 * typeSize, tc.type); break; } } } // // Texcoord 1 // { const auto& tc = g_gl.texCoordArray[1]; if (tc.enabled && tc.ptr) { const size_t typeSize = (size_t)QD3D12_TypeSize(tc.type); const size_t elemSize = (size_t)tc.size * typeSize; const size_t stride = tc.stride ? (size_t)tc.stride : elemSize; const uint8_t* p = tc.ptr + stride * (size_t)idx; switch (tc.type) { case GL_FLOAT: { const float* f = (const float*)p; if (tc.size > 0) out.u1 = f[0]; if (tc.size > 1) out.v1 = f[1]; break; } case GL_DOUBLE: { const double* f = (const double*)p; if (tc.size > 0) out.u1 = (float)f[0]; if (tc.size > 1) out.v1 = (float)f[1]; break; } default: if (tc.size > 0) out.u1 = QD3D12_ReadScalarFast(p + 0 * typeSize, tc.type); if (tc.size > 1) out.v1 = QD3D12_ReadScalarFast(p + 1 * typeSize, tc.type); break; } } } } static D3D12_PRIMITIVE_TOPOLOGY GetDrawTopology(GLenum originalMode) { switch (originalMode) { case GL_POINTS: return D3D_PRIMITIVE_TOPOLOGY_POINTLIST; case GL_LINES: return D3D_PRIMITIVE_TOPOLOGY_LINELIST; case GL_LINE_STRIP: case GL_LINE_LOOP: return D3D_PRIMITIVE_TOPOLOGY_LINELIST; // we manually convert this, so im adjusting to be a linelist. case GL_TRIANGLE_STRIP: // FlushImmediate expands strips into standalone triangles, so the IA sees a list. return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; case GL_TRIANGLES: default: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; } } static Mat4 QD3D12_GetPreviousMVPForObject(GLuint objectId, const Mat4& currentMvp); static float MapAlphaFunc(GLenum func); static float MapTexCombineMode(GLenum mode); static float MapTexCombineSource(GLenum source); static float MapTexCombineOperandRGB(GLenum operand); static float MapTexCombineOperandAlpha(GLenum operand); static bool QD3D12_ShouldEnableNormalMapTessellation(GLenum originalMode, const BatchKey& key); static bool QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(GLenum originalMode, size_t vertexCount); static bool QD3D12_UseNormalMapTessellationPSO(const BatchKey& key, bool nativeColorOnly); static D3D12_PRIMITIVE_TOPOLOGY_TYPE QD3D12_EffectiveTopologyTypeForPSO(const BatchKey& key, bool nativeColorOnly); static D3D12_PRIMITIVE_TOPOLOGY QD3D12_EffectiveIATopologyForDraw(const BatchKey& key, bool nativeColorOnly); static UINT8 QD3D12_CurrentColorWriteMask() { UINT8 mask = 0; if (g_gl.colorMaskR) mask |= D3D12_COLOR_WRITE_ENABLE_RED; if (g_gl.colorMaskG) mask |= D3D12_COLOR_WRITE_ENABLE_GREEN; if (g_gl.colorMaskB) mask |= D3D12_COLOR_WRITE_ENABLE_BLUE; if (g_gl.colorMaskA) mask |= D3D12_COLOR_WRITE_ENABLE_ALPHA; return mask; } static UINT8 QD3D12_ClampStencilMask(GLuint mask) { return (UINT8)(mask & 0xFFu); } static UINT8 QD3D12_ClampStencilRef(GLint ref) { return (UINT8)(ClampValue(ref, 0, 255) & 0xFF); } static void QD3D12_FillTexCombineKey(BatchKey& key, UINT unit) { float* rgb = (unit == 0) ? key.texComb0RGB : key.texComb1RGB; float* alpha = (unit == 0) ? key.texComb0Alpha : key.texComb1Alpha; float* operand = (unit == 0) ? key.texComb0Operand : key.texComb1Operand; float* color = (unit == 0) ? key.texEnvColor0 : key.texEnvColor1; rgb[0] = MapTexCombineMode(g_gl.texCombineRGB[unit]); rgb[1] = MapTexCombineSource(g_gl.texSource0RGB[unit]); rgb[2] = MapTexCombineSource(g_gl.texSource1RGB[unit]); rgb[3] = g_gl.texRGBScale[unit]; alpha[0] = MapTexCombineMode(g_gl.texCombineAlpha[unit]); alpha[1] = MapTexCombineSource(g_gl.texSource0Alpha[unit]); alpha[2] = MapTexCombineSource(g_gl.texSource1Alpha[unit]); alpha[3] = g_gl.texAlphaScale[unit]; operand[0] = MapTexCombineOperandRGB(g_gl.texOperand0RGB[unit]); operand[1] = MapTexCombineOperandRGB(g_gl.texOperand1RGB[unit]); operand[2] = MapTexCombineOperandAlpha(g_gl.texOperand0Alpha[unit]); operand[3] = MapTexCombineOperandAlpha(g_gl.texOperand1Alpha[unit]); color[0] = g_gl.texEnvColor[unit][0]; color[1] = g_gl.texEnvColor[unit][1]; color[2] = g_gl.texEnvColor[unit][2]; color[3] = g_gl.texEnvColor[unit][3]; } static bool QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(const TextureResource* tex) { if (!tex || tex == &g_gl.whiteTexture || tex->glId == 0) return false; // Tagged/explicit material maps are sampled through their dedicated shader // slots. Do not also feed them into fixed-function texture unit 0/1, or a // normal/glow/specular map can replace the diffuse/lightmap color stage. if (tex->isNormalMap || tex->isGlowMap || tex->isSpecularMap) return true; return tex->glId == g_gl.currentNormalMapTexture || tex->glId == g_gl.currentGlowMapTexture || tex->glId == g_gl.currentSpecularMapTexture; } static bool QD3D12_PolygonOffsetEnabledForMode(GLenum originalMode) { switch (originalMode) { case GL_POINTS: return g_gl.polygonOffsetPoint; case GL_LINES: case GL_LINE_STRIP: case GL_LINE_LOOP: return g_gl.polygonOffsetLine; case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: case GL_QUADS: case GL_QUAD_STRIP: case GL_POLYGON: default: return g_gl.polygonOffsetFill; } } #ifdef _DEBUG #pragma optimize off #endif static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource* tex0, const TextureResource* tex1, TextureResource* const* allTextures) { const bool tex0IsMaterialMapOnly = QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(tex0); const bool tex1IsMaterialMapOnly = QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(tex1); const bool useTex0 = g_gl.texture2D[0] && !tex0IsMaterialMapOnly; const bool useTex1 = g_gl.texture2D[1] && !tex1IsMaterialMapOnly; BatchKey key{}; key.pipeline = PickPipeline(useTex0, useTex1); key.topology = GetDrawTopology(originalMode); key.tex0SrvIndex = (useTex0 && tex0 && tex0->srvIndex != UINT_MAX) ? tex0->srvIndex : g_gl.whiteTexture.srvIndex; key.tex1SrvIndex = (useTex1 && tex1 && tex1->srvIndex != UINT_MAX) ? tex1->srvIndex : g_gl.whiteTexture.srvIndex; for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) key.textureSrvIndex[i] = (allTextures && allTextures[i]) ? allTextures[i]->srvIndex : 0; TextureResource* normalMap = QD3D12_SelectNormalMapTexture(allTextures); if (normalMap && normalMap->texture && normalMap->srvIndex != UINT_MAX && normalMap->gpuValid) { key.normalMapSrvIndex = normalMap->srvIndex; key.useNormalMap = 1.0f; } else { key.normalMapSrvIndex = g_gl.whiteTexture.srvIndex; key.useNormalMap = 0.0f; } key.normalMapStrength = g_gl.currentNormalMapStrength; key.normalMapYSign = g_gl.currentNormalMapYSign; key.useTessellation = false; 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; TextureResource* specularMap = QD3D12_SelectSpecularMapTexture(allTextures); if (specularMap && specularMap->texture && specularMap->srvIndex != UINT_MAX) { key.specularMapSrvIndex = specularMap->srvIndex; key.useSpecularMap = 1.0f; } else { key.specularMapSrvIndex = g_gl.whiteTexture.srvIndex; key.useSpecularMap = 0.0f; } key.specularMapStrength = g_gl.currentSpecularMapStrength; TextureResource* neuralPOMTex = nullptr; if (g_gl.neuralPOMEnabled) { if (g_gl.currentNeuralPOMTexture != 0) neuralPOMTex = QD3D12_FindTextureResource(g_gl.currentNeuralPOMTexture); if (!neuralPOMTex || !QD3D12_TextureHasNeuralPOMData(*neuralPOMTex)) neuralPOMTex = normalMap; if ((!neuralPOMTex || !QD3D12_TextureHasNeuralPOMData(*neuralPOMTex)) && allTextures) { for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { TextureResource* candidate = allTextures[unit]; if (candidate && candidate != &g_gl.whiteTexture && QD3D12_TextureHasNeuralPOMData(*candidate)) { neuralPOMTex = candidate; break; } } } } if (neuralPOMTex && neuralPOMTex->neuralPOM.gpuValid && neuralPOMTex->neuralPOM.weightsSrvIndex != UINT_MAX && neuralPOMTex->neuralPOM.latentSrvIndex != UINT_MAX) { key.neuralPOMWeightsSrvIndex = neuralPOMTex->neuralPOM.weightsSrvIndex; key.neuralPOMLatentSrvIndex = neuralPOMTex->neuralPOM.latentSrvIndex; key.useNeuralPOM = 1.0f; } else { const UINT fallbackSrv = QD3D12_NeuralPOMFallbackSrvIndex(); key.neuralPOMWeightsSrvIndex = fallbackSrv; key.neuralPOMLatentSrvIndex = fallbackSrv; key.useNeuralPOM = 0.0f; } key.useARBPrograms = QD3D12ARB_IsActive(); if (key.useARBPrograms) { key.arbVertexProgram = QD3D12ARB_GetBoundVertexProgram(); key.arbFragmentProgram = QD3D12ARB_GetBoundFragmentProgram(); key.arbVertexRevision = QD3D12ARB_GetBoundVertexRevision(); key.arbFragmentRevision = QD3D12ARB_GetBoundFragmentRevision(); key.arbVertexBlob = QD3D12ARB_GetVertexShaderBlob(); key.arbFragmentBlob = QD3D12ARB_GetFragmentShaderBlob(); QD3D12ARB_FillDrawConstantArrays(&key.arbConstants); if (!key.arbVertexBlob || !key.arbFragmentBlob) key.useARBPrograms = false; } key.alphaRef = g_gl.alphaRef; key.alphaFunc = MapAlphaFunc(g_gl.alphaFunc); key.useTex0 = useTex0 ? 1.0f : 0.0f; key.useTex1 = useTex1 ? 1.0f : 0.0f; key.viewport = g_currentWindow->viewport; key.scissor = g_currentWindow->scissor; key.tex1IsLightmap = useTex1 ? 1.0f : 0.0f; key.texEnvMode0 = MapTexEnvMode(g_gl.texEnvMode[0]); key.texEnvMode1 = MapTexEnvMode(g_gl.texEnvMode[1]); key.fogEnabled = g_gl.fog ? 1.0f : 0.0f; switch (g_gl.fogMode) { case GL_LINEAR: key.fogMode = 0.0f; break; case GL_EXP: key.fogMode = 1.0f; break; case GL_EXP2: key.fogMode = 2.0f; break; default: key.fogMode = 1.0f; break; } key.fogDensity = g_gl.fogDensity; key.fogStart = g_gl.fogStart; key.fogEnd = g_gl.fogEnd; key.fogColor[0] = g_gl.fogColor[0]; key.fogColor[1] = g_gl.fogColor[1]; key.fogColor[2] = g_gl.fogColor[2]; key.fogColor[3] = g_gl.fogColor[3]; QD3D12_FillTexCombineKey(key, 0); QD3D12_FillTexCombineKey(key, 1); key.colorWriteMask = QD3D12_CurrentColorWriteMask(); key.cullFaceEnabled = g_gl.cullFace; key.cullMode = g_gl.cullMode; key.frontFace = g_gl.frontFace; key.stencilTest = g_gl.stencilTest; key.stencilReadMask = QD3D12_ClampStencilMask(g_gl.stencilFrontFuncMask & g_gl.stencilBackFuncMask); key.stencilWriteMask = QD3D12_ClampStencilMask(g_gl.stencilFrontMask | g_gl.stencilBackMask); key.stencilRef = QD3D12_ClampStencilRef(g_gl.stencilFrontRef); key.stencilFrontFunc = g_gl.stencilFrontFunc; key.stencilFrontSFail = g_gl.stencilFrontSFail; key.stencilFrontDPFail = g_gl.stencilFrontDPFail; key.stencilFrontDPPass = g_gl.stencilFrontDPPass; key.stencilBackFunc = g_gl.stencilBackFunc; key.stencilBackSFail = g_gl.stencilBackSFail; key.stencilBackDPFail = g_gl.stencilBackDPFail; key.stencilBackDPPass = g_gl.stencilBackDPPass; key.depthBoundsTest = g_gl.depthBoundsTest; key.depthBoundsMin = (float)g_gl.depthBoundsMin; key.depthBoundsMax = (float)g_gl.depthBoundsMax; key.polygonOffsetEnabled = QD3D12_PolygonOffsetEnabledForMode(originalMode); if (key.polygonOffsetEnabled) { key.polygonOffsetFactor = g_gl.polygonOffsetFactor; key.polygonOffsetUnits = g_gl.polygonOffsetUnits; } key.blendSrc = g_gl.blendSrc; key.blendDst = g_gl.blendDst; key.depthTest = g_gl.depthTest; key.depthWrite = g_gl.depthWrite; key.depthFunc = g_gl.depthFunc; key.mvp = CurrentMVP(); key.motionObjectId = g_gl.currentMotionObjectId; key.prevMvp = QD3D12_GetPreviousMVPForObject(key.motionObjectId, key.mvp); key.modelMatrix = CurrentModelMatrix(); const bool cameraUpdatedForBatch = QD3D12_UpdateCameraInfoFromCurrentMatrices(); key.cameraValid = (cameraUpdatedForBatch && g_gl.cameraState.valid) ? 1.0f : 0.0f; key.cameraWorldPos[0] = g_gl.cameraState.cameraPos[0]; key.cameraWorldPos[1] = g_gl.cameraState.cameraPos[1]; key.cameraWorldPos[2] = g_gl.cameraState.cameraPos[2]; key.geometryFlag = QD3D12_CurrentEffectiveGeometryFlag(); key.roughness = g_gl.currentSurfaceRoughness; key.materialType = QD3D12_CurrentEffectiveMaterialType(); key.useTessellation = QD3D12_ShouldEnableNormalMapTessellation(originalMode, key); if (key.useNeuralPOM > 0.5f) key.useTessellation = false; if (key.useARBPrograms) key.useTessellation = false; g_gl.currObjectMVPs[key.motionObjectId] = key.mvp; return key; } #ifdef _DEBUG #pragma optimize on #endif static Mat4 QD3D12_GetPreviousMVPForObject(GLuint objectId, const Mat4& currentMvp) { auto it = g_gl.prevObjectMVPs.find(objectId); if (it != g_gl.prevObjectMVPs.end()) return it->second; return currentMvp; } static void QD3D12_CommitMotionHistoryForFrame() { g_gl.prevObjectMVPs = g_gl.currObjectMVPs; g_gl.currObjectMVPs.clear(); g_gl.motionHistoryReset = false; g_gl.prevJitterX = g_gl.jitterX; g_gl.prevJitterY = g_gl.jitterY; ++g_gl.frameSerial; } void APIENTRY glSelectTextureSGIS(GLenum texture) { switch (texture) { case GL_TEXTURE0_SGIS: g_gl.activeTextureUnit = 0; break; case GL_TEXTURE1_SGIS: g_gl.activeTextureUnit = 1; break; default: g_gl.activeTextureUnit = 0; break; } } void APIENTRY glMTexCoord2fSGIS(GLenum texture, GLfloat s, GLfloat t) { GLuint oldUnit = g_gl.activeTextureUnit; switch (texture) { case GL_TEXTURE0_SGIS: g_gl.activeTextureUnit = 0; break; case GL_TEXTURE1_SGIS: g_gl.activeTextureUnit = 1; break; default: g_gl.activeTextureUnit = 0; break; } g_gl.curU[g_gl.activeTextureUnit] = s; g_gl.curV[g_gl.activeTextureUnit] = t; g_gl.activeTextureUnit = oldUnit; } void APIENTRY glActiveTextureARB(GLenum texture) { if (texture >= GL_TEXTURE0_ARB) g_gl.activeTextureUnit = ClampValue((GLuint)(texture - GL_TEXTURE0_ARB), 0, QD3D12_MaxTextureUnits - 1); else g_gl.activeTextureUnit = 0; } void APIENTRY glMultiTexCoord2fARB(GLenum texture, GLfloat s, GLfloat t) { GLuint unit = 0; if (texture >= GL_TEXTURE0_ARB) unit = ClampValue((GLuint)(texture - GL_TEXTURE0_ARB), 0, QD3D12_MaxTextureUnits - 1); g_gl.curU[unit] = s; g_gl.curV[unit] = t; } // ============================================================ // SECTION 5: utility mapping // ============================================================ static std::vector& QD3D12_CurrentMatrixStack() { switch (g_gl.matrixMode) { case GL_PROJECTION: return g_gl.projStack; case GL_TEXTURE: return g_gl.texStack[g_gl.activeTextureUnit]; case GL_MODELVIEW: default: return g_gl.modelStack; } } static int BytesPerPixel(GLenum format, GLenum type) { if (type != GL_UNSIGNED_BYTE) return 4; switch (format) { case GL_ALPHA: case GL_LUMINANCE: case GL_INTENSITY: return 1; case GL_RGB: return 3; case GL_RGBA: default: return 4; } } static DXGI_FORMAT MapTextureFormat(GLenum format) { (void)format; // The fixed-function shader path samples RGBA for all legacy uncompressed // texture formats. Alpha, luminance, and intensity are expanded to RGBA8 // in UploadTexture(), so the D3D12 resource must also be RGBA8. return DXGI_FORMAT_R8G8B8A8_UNORM; } static bool QD3D12_IsCompressedTextureFormat(GLenum internalFormat) { switch (internalFormat) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return true; default: return false; } } static DXGI_FORMAT QD3D12_MapCompressedTextureFormat(GLenum internalFormat) { switch (internalFormat) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return DXGI_FORMAT_BC1_UNORM; case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return DXGI_FORMAT_BC2_UNORM; case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return DXGI_FORMAT_BC3_UNORM; default: return DXGI_FORMAT_UNKNOWN; } } static UINT QD3D12_CompressedTextureBlockBytes(GLenum internalFormat) { switch (internalFormat) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 8; case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 16; default: return 0; } } static UINT QD3D12_CompressedTextureBlocksWide(GLsizei width) { return std::max(1u, ((UINT)width + 3u) / 4u); } static UINT QD3D12_CompressedTextureBlocksHigh(GLsizei height) { return std::max(1u, ((UINT)height + 3u) / 4u); } static UINT QD3D12_CompressedTextureImageSize(GLsizei width, GLsizei height, GLenum internalFormat) { const UINT blockBytes = QD3D12_CompressedTextureBlockBytes(internalFormat); if (width <= 0 || height <= 0 || blockBytes == 0) return 0; const UINT blocksWide = QD3D12_CompressedTextureBlocksWide(width); const UINT blocksHigh = QD3D12_CompressedTextureBlocksHigh(height); return blocksWide * blocksHigh * blockBytes; } static UINT QD3D12_TextureShaderComponentMapping(const TextureResource& tex) { if (!tex.forceOpaqueAlpha) return D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; return D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING( D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0, D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1, D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2, D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1); } static float MapTexCombineMode(GLenum mode) { switch (mode) { case GL_REPLACE: return 1.0f; case GL_MODULATE: return 2.0f; #ifdef GL_ADD case GL_ADD: return 3.0f; #endif #ifdef GL_ADD_SIGNED_ARB case GL_ADD_SIGNED_ARB: return 4.0f; #endif default: return 2.0f; } } static float MapTexCombineSource(GLenum source) { switch (source) { case GL_TEXTURE: return 0.0f; #ifdef GL_PRIMARY_COLOR_ARB case GL_PRIMARY_COLOR_ARB: return 1.0f; #endif #ifdef GL_PREVIOUS_ARB case GL_PREVIOUS_ARB: return 2.0f; #endif #ifdef GL_CONSTANT_ARB case GL_CONSTANT_ARB: return 3.0f; #endif default: return 0.0f; } } static float MapTexCombineOperandRGB(GLenum operand) { switch (operand) { case GL_SRC_COLOR: return 0.0f; case GL_ONE_MINUS_SRC_COLOR: return 1.0f; case GL_SRC_ALPHA: return 2.0f; case GL_ONE_MINUS_SRC_ALPHA: return 3.0f; default: return 0.0f; } } static float MapTexCombineOperandAlpha(GLenum operand) { switch (operand) { case GL_SRC_ALPHA: return 0.0f; case GL_ONE_MINUS_SRC_ALPHA: return 1.0f; case GL_SRC_COLOR: return 2.0f; case GL_ONE_MINUS_SRC_COLOR: return 3.0f; default: return 0.0f; } } static D3D12_BLEND MapBlendAlpha(GLenum v) { switch (v) { case GL_ZERO: return D3D12_BLEND_ZERO; case GL_ONE: return D3D12_BLEND_ONE; case GL_SRC_ALPHA: return D3D12_BLEND_SRC_ALPHA; case GL_ONE_MINUS_SRC_ALPHA: return D3D12_BLEND_INV_SRC_ALPHA; case GL_DST_ALPHA: return D3D12_BLEND_DEST_ALPHA; case GL_ONE_MINUS_DST_ALPHA: return D3D12_BLEND_INV_DEST_ALPHA; // Color factors are illegal in alpha slots. // Fold them to something reasonable. case GL_SRC_COLOR: return D3D12_BLEND_SRC_ALPHA; case GL_ONE_MINUS_SRC_COLOR: return D3D12_BLEND_INV_SRC_ALPHA; case GL_DST_COLOR: return D3D12_BLEND_DEST_ALPHA; case GL_ONE_MINUS_DST_COLOR: return D3D12_BLEND_INV_DEST_ALPHA; case GL_SRC_ALPHA_SATURATE: return D3D12_BLEND_ONE; default: return D3D12_BLEND_ONE; } } static D3D12_BLEND MapBlend(GLenum v) { switch (v) { case GL_ZERO: return D3D12_BLEND_ZERO; case GL_ONE: return D3D12_BLEND_ONE; case GL_SRC_COLOR: return D3D12_BLEND_SRC_COLOR; case GL_ONE_MINUS_SRC_COLOR: return D3D12_BLEND_INV_SRC_COLOR; case GL_DST_COLOR: return D3D12_BLEND_DEST_COLOR; case GL_ONE_MINUS_DST_COLOR: return D3D12_BLEND_INV_DEST_COLOR; case GL_SRC_ALPHA: return D3D12_BLEND_SRC_ALPHA; case GL_ONE_MINUS_SRC_ALPHA: return D3D12_BLEND_INV_SRC_ALPHA; case GL_DST_ALPHA: return D3D12_BLEND_DEST_ALPHA; case GL_ONE_MINUS_DST_ALPHA: return D3D12_BLEND_INV_DEST_ALPHA; case GL_SRC_ALPHA_SATURATE: return D3D12_BLEND_SRC_ALPHA_SAT; default: return D3D12_BLEND_ONE; } } static float MapAlphaFunc(GLenum func) { switch (func) { case GL_NEVER: return 0.0f; case GL_LESS: return 1.0f; case GL_EQUAL: return 2.0f; case GL_LEQUAL: return 3.0f; case GL_GREATER: return 4.0f; case GL_NOTEQUAL: return 5.0f; case GL_GEQUAL: return 6.0f; case GL_ALWAYS: return 7.0f; default: return 4.0f; // Match legacy default: GL_GREATER. } } static D3D12_COMPARISON_FUNC MapCompare(GLenum f) { switch (f) { case GL_NEVER: return D3D12_COMPARISON_FUNC_NEVER; case GL_LESS: return D3D12_COMPARISON_FUNC_LESS; case GL_EQUAL: return D3D12_COMPARISON_FUNC_EQUAL; case GL_LEQUAL: return D3D12_COMPARISON_FUNC_LESS_EQUAL; case GL_GREATER: return D3D12_COMPARISON_FUNC_GREATER; case GL_NOTEQUAL: return D3D12_COMPARISON_FUNC_NOT_EQUAL; case GL_GEQUAL: return D3D12_COMPARISON_FUNC_GREATER_EQUAL; case GL_ALWAYS: return D3D12_COMPARISON_FUNC_ALWAYS; default: return D3D12_COMPARISON_FUNC_ALWAYS; } } static D3D12_CULL_MODE MapCull(GLenum m); static D3D12_STENCIL_OP MapStencilOp(GLenum op) { switch (op) { case GL_KEEP: return D3D12_STENCIL_OP_KEEP; case GL_ZERO: return D3D12_STENCIL_OP_ZERO; case GL_REPLACE: return D3D12_STENCIL_OP_REPLACE; case GL_INCR: return D3D12_STENCIL_OP_INCR_SAT; case GL_DECR: return D3D12_STENCIL_OP_DECR_SAT; #ifdef GL_INCR_WRAP case GL_INCR_WRAP: return D3D12_STENCIL_OP_INCR; #endif #ifdef GL_DECR_WRAP case GL_DECR_WRAP: return D3D12_STENCIL_OP_DECR; #endif case GL_INVERT: return D3D12_STENCIL_OP_INVERT; default: return D3D12_STENCIL_OP_KEEP; } } static D3D12_DEPTH_STENCILOP_DESC BuildStencilFaceDesc(GLenum func, GLenum sfail, GLenum dpfail, GLenum dppass) { D3D12_DEPTH_STENCILOP_DESC d{}; d.StencilFailOp = MapStencilOp(sfail); d.StencilDepthFailOp = MapStencilOp(dpfail); d.StencilPassOp = MapStencilOp(dppass); d.StencilFunc = MapCompare(func); return d; } static INT QD3D12_MapPolygonOffsetUnitsToDepthBias(float units) { if (!std::isfinite(units) || fabsf(units) <= 1.0e-7f) return 0; const float clamped = ClampValue(units, -16777216.0f, 16777216.0f); INT bias = (INT)std::lround((double)clamped); // D3D12 only exposes the constant term as an integer. Preserve sub-unit GL // offsets instead of silently rounding a small but intentional offset to zero. if (bias == 0) bias = (clamped > 0.0f) ? 1 : -1; return bias; } static void ApplyRasterDepthStencilState(D3D12_GRAPHICS_PIPELINE_STATE_DESC& d, const BatchKey& key) { d.RasterizerState.CullMode = key.cullFaceEnabled ? MapCull(key.cullMode) : D3D12_CULL_MODE_NONE; d.RasterizerState.FrontCounterClockwise = (key.frontFace == GL_CCW) ? TRUE : FALSE; if (key.polygonOffsetEnabled) { // OpenGL polygon offset is: depth += m * factor + r * units. // D3D12's slope and integer depth-bias terms map to the same two pieces. d.RasterizerState.SlopeScaledDepthBias = key.polygonOffsetFactor; d.RasterizerState.DepthBias = QD3D12_MapPolygonOffsetUnitsToDepthBias(key.polygonOffsetUnits); d.RasterizerState.DepthBiasClamp = 0.0f; } else { d.RasterizerState.SlopeScaledDepthBias = 0.0f; d.RasterizerState.DepthBias = 0; d.RasterizerState.DepthBiasClamp = 0.0f; } d.DepthStencilState.DepthEnable = key.depthTest ? TRUE : FALSE; d.DepthStencilState.DepthWriteMask = key.depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; d.DepthStencilState.DepthFunc = MapCompare(key.depthFunc); d.DepthStencilState.StencilEnable = key.stencilTest ? TRUE : FALSE; d.DepthStencilState.StencilReadMask = key.stencilReadMask; d.DepthStencilState.StencilWriteMask = key.stencilWriteMask; d.DepthStencilState.FrontFace = BuildStencilFaceDesc(key.stencilFrontFunc, key.stencilFrontSFail, key.stencilFrontDPFail, key.stencilFrontDPPass); d.DepthStencilState.BackFace = BuildStencilFaceDesc(key.stencilBackFunc, key.stencilBackSFail, key.stencilBackDPFail, key.stencilBackDPPass); } static D3D12_CULL_MODE MapCull(GLenum m) { switch (m) { case GL_FRONT: return D3D12_CULL_MODE_FRONT; case GL_BACK: return D3D12_CULL_MODE_BACK; default: return D3D12_CULL_MODE_NONE; } } static D3D12_FILTER MapFilter(GLenum minFilter, GLenum magFilter) { const bool linearMin = (minFilter == GL_LINEAR || minFilter == GL_LINEAR_MIPMAP_NEAREST || minFilter == GL_LINEAR_MIPMAP_LINEAR); const bool linearMag = (magFilter == GL_LINEAR); if (linearMin && linearMag) return D3D12_FILTER_MIN_MAG_MIP_LINEAR; if (!linearMin && !linearMag) return D3D12_FILTER_MIN_MAG_MIP_POINT; if (linearMin && !linearMag) return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT; return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT; } static D3D12_TEXTURE_ADDRESS_MODE MapAddress(GLenum wrap) { switch (wrap) { case GL_CLAMP: return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; case GL_REPEAT: default: return D3D12_TEXTURE_ADDRESS_MODE_WRAP; } } static D3D12_PRIMITIVE_TOPOLOGY MapPrimitive(GLenum mode) { switch (mode) { case GL_POINTS: return D3D_PRIMITIVE_TOPOLOGY_POINTLIST; case GL_LINES: return D3D_PRIMITIVE_TOPOLOGY_LINELIST; case GL_LINE_STRIP: return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP; case GL_TRIANGLES: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; case GL_TRIANGLE_STRIP: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; default: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; } } static D3D12_PRIMITIVE_TOPOLOGY_TYPE GetTopologyTypeFromTopology(D3D12_PRIMITIVE_TOPOLOGY topo) { switch (topo) { case D3D_PRIMITIVE_TOPOLOGY_POINTLIST: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; case D3D_PRIMITIVE_TOPOLOGY_LINELIST: case D3D_PRIMITIVE_TOPOLOGY_LINESTRIP: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; case D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST: case D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP: default: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; } } static bool QD3D12_OriginalModeCanUseNormalMapTessellation(GLenum originalMode) { switch (originalMode) { case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: case GL_QUADS: case GL_QUAD_STRIP: case GL_POLYGON: return true; default: return false; } } static bool QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(GLenum originalMode, size_t vertexCount) { if (!QD3D12_OriginalModeCanUseNormalMapTessellation(originalMode)) return false; switch (originalMode) { case GL_TRIANGLES: return vertexCount >= 3 && (vertexCount % 3) == 0; case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: case GL_POLYGON: return vertexCount >= 3; case GL_QUADS: case GL_QUAD_STRIP: return vertexCount >= 4; default: return false; } } static bool QD3D12_CurrentDrawLooks3DForTessellation() { if (g_gl.projStack.empty()) return false; // UI/HUD paths in these engines normally use glOrtho. Keep tessellation out // of those 2D passes and only allow it under a perspective projection. return QD3D12_IsPerspectiveProjectionCM(g_gl.projStack.back().m); } static bool QD3D12_ShouldEnableNormalMapTessellation(GLenum originalMode, const BatchKey& key) { if (key.useARBPrograms) return false; if (key.useNormalMap <= 0.5f) return false; if (key.normalMapSrvIndex == UINT_MAX || key.normalMapSrvIndex == g_gl.whiteTexture.srvIndex) return false; if (key.topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST) return false; // Keep decals, alpha-tested cutouts, translucent passes, post-upscale overlays, // shadow/color-mask-only work, and depth-disabled geometry on the proven VS/PS route. if (key.pipeline != PIPE_OPAQUE_TEX && key.pipeline != PIPE_OPAQUE_UNTEX) return false; if (!key.depthTest || !key.depthWrite || key.colorWriteMask == 0) return false; if (!QD3D12_OriginalModeCanUseNormalMapTessellation(originalMode)) return false; if (!QD3D12_CurrentDrawLooks3DForTessellation()) return false; if (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE) return false; return true; } static bool QD3D12_UseNormalMapTessellationPSO(const BatchKey& key, bool nativeColorOnly) { return key.useTessellation && !nativeColorOnly && !key.useARBPrograms && (key.pipeline == PIPE_OPAQUE_TEX || key.pipeline == PIPE_OPAQUE_UNTEX) && key.depthTest && key.depthWrite && key.useNormalMap > 0.5f && key.normalMapSrvIndex != UINT_MAX && key.normalMapSrvIndex != g_gl.whiteTexture.srvIndex && key.topology == D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; } static D3D12_PRIMITIVE_TOPOLOGY_TYPE QD3D12_EffectiveTopologyTypeForPSO(const BatchKey& key, bool nativeColorOnly) { return QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) ? D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH : GetTopologyTypeFromTopology(key.topology); } static D3D12_PRIMITIVE_TOPOLOGY QD3D12_EffectiveIATopologyForDraw(const BatchKey& key, bool nativeColorOnly) { return QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) ? D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST : key.topology; } // ============================================================ // SECTION 6: upload helpers // ============================================================ static void QD3D12_WaitForGPU() { const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } } static void QD3D12_WaitForFrame(UINT frameIndex) { FrameResources& fr = g_currentWindow->frames[frameIndex]; if (fr.fenceValue != 0 && g_gl.fence->GetCompletedValue() < fr.fenceValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(fr.fenceValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } } static void QD3D12_ResetUploadRing() { if (!g_currentWindow) return; g_currentWindow->upload.offset = 0; } struct UploadAlloc { void* cpu = nullptr; D3D12_GPU_VIRTUAL_ADDRESS gpu = 0; UINT offset = 0; }; static inline UINT QD3D12_AlignUpUINT(UINT value, UINT alignment) { if (alignment == 0) alignment = 1; return (value + (alignment - 1)) & ~(alignment - 1); } static inline UINT64 QD3D12_AlignUpUINT64(UINT64 value, UINT64 alignment) { if (alignment == 0) alignment = 1; return (value + (alignment - 1)) & ~(alignment - 1); } static bool QD3D12_DrainUploadRingAndContinue() { if (!g_currentWindow || !g_gl.cmdList || !g_gl.queue || !g_gl.fence) return false; QD3D12Window& w = *g_currentWindow; // Submit the work recorded so far, wait for it, then reuse this frame's // upload resource from offset 0. This keeps texture streaming / huge UI // uploads from hard-failing when a frame exceeds the ring size. QD3D12_ExecuteMainCommandListAndWait(w); QD3D12_ResetUploadRing(); // Command-list state is reset by ExecuteMainCommandListAndWait(). // Re-bind the current phase targets so subsequent copies/draws continue. QD3D12_BindTargetsForCurrentPhase(w); return true; } static bool QD3D12_EnsureUploadSpace(UINT bytes, UINT alignment) { if (!g_currentWindow) return false; UploadRing& upload = g_currentWindow->upload; if (bytes > upload.size) { QD3D12_Fatal( "Single upload allocation too large: need %u bytes, upload ring size %u", bytes, upload.size); return false; } const UINT alignedOffset = QD3D12_AlignUpUINT(upload.offset, alignment); if ((UINT64)alignedOffset + (UINT64)bytes <= (UINT64)upload.size) return false; if (!QD3D12_DrainUploadRingAndContinue()) { QD3D12_Fatal( "Per-frame upload buffer overflow: need %u bytes, aligned offset %u, size %u", bytes, alignedOffset, upload.size); return false; } const UINT retryOffset = QD3D12_AlignUpUINT(upload.offset, alignment); if ((UINT64)retryOffset + (UINT64)bytes > (UINT64)upload.size) { QD3D12_Fatal( "Upload allocation still does not fit after drain: need %u bytes, aligned offset %u, size %u", bytes, retryOffset, upload.size); } return true; } static bool QD3D12_EnsureUploadSpaceForDraw(UINT vbBytes, UINT cbBytes) { if (!g_currentWindow) return false; UploadRing& upload = g_currentWindow->upload; if (vbBytes > upload.size || cbBytes > upload.size) { QD3D12_Fatal( "Single draw upload too large: vb=%u cb=%u upload ring size=%u", vbBytes, cbBytes, upload.size); return false; } UINT64 cursor = upload.offset; const UINT64 vbOffset = QD3D12_AlignUpUINT64(cursor, 256); cursor = vbOffset + (UINT64)vbBytes; const UINT64 cbOffset = QD3D12_AlignUpUINT64(cursor, 256); cursor = cbOffset + (UINT64)cbBytes; if (cursor <= (UINT64)upload.size) return false; if (!QD3D12_DrainUploadRingAndContinue()) { QD3D12_Fatal( "Per-frame upload buffer overflow during draw upload: vb=%u cb=%u offset=%u size=%u", vbBytes, cbBytes, upload.offset, upload.size); return false; } cursor = upload.offset; const UINT64 retryVbOffset = QD3D12_AlignUpUINT64(cursor, 256); cursor = retryVbOffset + (UINT64)vbBytes; const UINT64 retryCbOffset = QD3D12_AlignUpUINT64(cursor, 256); cursor = retryCbOffset + (UINT64)cbBytes; if (cursor > (UINT64)upload.size) { QD3D12_Fatal( "Draw upload still does not fit after drain: vb=%u cb=%u upload ring size=%u", vbBytes, cbBytes, upload.size); } return true; } static UploadAlloc QD3D12_AllocUpload(UINT bytes, UINT alignment) { if (alignment == 0) alignment = 1; QD3D12_EnsureUploadSpace(bytes, alignment); UploadRing& upload = g_currentWindow->upload; UINT alignedOffset = QD3D12_AlignUpUINT(upload.offset, alignment); if ((UINT64)alignedOffset + (UINT64)bytes > (UINT64)upload.size) { QD3D12_Fatal( "Per-frame upload buffer overflow: need %u bytes, aligned offset %u, size %u", bytes, alignedOffset, upload.size); } UploadAlloc out; out.offset = alignedOffset; out.cpu = upload.cpuBase[g_currentWindow->frameIndex] + alignedOffset; out.gpu = upload.gpuBase[g_currentWindow->frameIndex] + alignedOffset; upload.offset = alignedOffset + bytes; return out; } static UINT64 QD3D12_AlignRawBufferBytes(UINT64 bytes) { return (bytes + 3ull) & ~3ull; } static bool QD3D12_TextureHasNeuralPOMData(const TextureResource& tex) { return tex.neuralPOM.hasData && !tex.neuralPOM.weightsBytes.empty() && !tex.neuralPOM.latentRGBA16FBytes.empty(); } static UINT QD3D12_NeuralPOMFallbackSrvIndex() { return (g_qd3d12NeuralPOMZeroSrvIndex != UINT_MAX) ? g_qd3d12NeuralPOMZeroSrvIndex : g_gl.whiteTexture.srvIndex; } static void QD3D12_CreateRawBufferSrv(ID3D12Resource* resource, UINT64 byteSize, D3D12_CPU_DESCRIPTOR_HANDLE cpu) { D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; srv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; srv.Format = DXGI_FORMAT_R32_TYPELESS; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.Buffer.FirstElement = 0; srv.Buffer.NumElements = (UINT)(QD3D12_AlignRawBufferBytes(byteSize) / 4ull); srv.Buffer.StructureByteStride = 0; srv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW; g_gl.device->CreateShaderResourceView(resource, &srv, cpu); } static bool QD3D12_EnsureNeuralRawBuffer( ComPtr& resource, D3D12_RESOURCE_STATES& state, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& srvCpu, D3D12_GPU_DESCRIPTOR_HANDLE& srvGpu, const std::vector& srcBytes) { if (!g_currentWindow || !g_gl.device || !g_gl.cmdList || srcBytes.empty()) return false; const UINT64 byteSize = QD3D12_AlignRawBufferBytes((UINT64)srcBytes.size()); bool recreate = false; if (!resource) recreate = true; else if (resource->GetDesc().Width != byteSize) { QD3D12_RetireResource(resource); recreate = true; } if (recreate) { D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = byteSize; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; rd.Flags = D3D12_RESOURCE_FLAG_NONE; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&resource))); state = D3D12_RESOURCE_STATE_COPY_DEST; if (srvIndex == UINT_MAX) { srvIndex = g_gl.nextSrvIndex++; srvCpu = QD3D12_SrvCpu(srvIndex); srvGpu = QD3D12_SrvGpu(srvIndex); } } if (srvIndex == UINT_MAX) { srvIndex = g_gl.nextSrvIndex++; srvCpu = QD3D12_SrvCpu(srvIndex); srvGpu = QD3D12_SrvGpu(srvIndex); } QD3D12_CreateRawBufferSrv(resource.Get(), byteSize, srvCpu); if (state != D3D12_RESOURCE_STATE_COPY_DEST) { D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = resource.Get(); b.Transition.StateBefore = state; b.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &b); state = D3D12_RESOURCE_STATE_COPY_DEST; } UploadAlloc upload = QD3D12_AllocUpload((UINT)byteSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); memset(upload.cpu, 0, (size_t)byteSize); memcpy(upload.cpu, srcBytes.data(), srcBytes.size()); g_gl.cmdList->CopyBufferRegion(resource.Get(), 0, g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(), upload.offset, byteSize); D3D12_RESOURCE_BARRIER toSrv{}; toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toSrv.Transition.pResource = resource.Get(); toSrv.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; toSrv.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &toSrv); state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; return true; } static bool QD3D12_UploadNeuralPOM(TextureResource& tex) { NeuralPOMResource& npom = tex.neuralPOM; if (!QD3D12_TextureHasNeuralPOMData(tex)) return false; if (npom.gpuValid && npom.gpuGeneration == npom.generation) return true; if (!QD3D12_EnsureNeuralRawBuffer( npom.weightsBuffer, npom.weightsState, npom.weightsSrvIndex, npom.weightsSrvCpu, npom.weightsSrvGpu, npom.weightsBytes)) { return false; } if (!QD3D12_EnsureNeuralRawBuffer( npom.latentBuffer, npom.latentState, npom.latentSrvIndex, npom.latentSrvCpu, npom.latentSrvGpu, npom.latentRGBA16FBytes)) { return false; } npom.gpuValid = true; npom.gpuGeneration = npom.generation; return true; } static void QD3D12_CreateNeuralPOMZeroBuffer() { if (g_qd3d12NeuralPOMZeroBuffer || !g_currentWindow || !g_gl.device || !g_gl.cmdList) return; const UINT64 byteSize = 256; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = byteSize; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&g_qd3d12NeuralPOMZeroBuffer))); g_qd3d12NeuralPOMZeroSrvIndex = g_gl.nextSrvIndex++; g_qd3d12NeuralPOMZeroSrvCpu = QD3D12_SrvCpu(g_qd3d12NeuralPOMZeroSrvIndex); g_qd3d12NeuralPOMZeroSrvGpu = QD3D12_SrvGpu(g_qd3d12NeuralPOMZeroSrvIndex); QD3D12_CreateRawBufferSrv(g_qd3d12NeuralPOMZeroBuffer.Get(), byteSize, g_qd3d12NeuralPOMZeroSrvCpu); QD3D12_CHECK(g_currentWindow->frames[g_currentWindow->frameIndex].cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(g_currentWindow->frames[g_currentWindow->frameIndex].cmdAlloc.Get(), nullptr)); UploadAlloc upload = QD3D12_AllocUpload((UINT)byteSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); memset(upload.cpu, 0, (size_t)byteSize); g_gl.cmdList->CopyBufferRegion(g_qd3d12NeuralPOMZeroBuffer.Get(), 0, g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(), upload.offset, byteSize); D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = g_qd3d12NeuralPOMZeroBuffer.Get(); b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &b); QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); QD3D12_WaitForGPU(); QD3D12_ResetUploadRing(); } // ============================================================ // SECTION 7: D3D12 initialization // ============================================================ static void QD3D12_CreateOcclusionQueryObjects() { D3D12_QUERY_HEAP_DESC qh{}; qh.Count = QD3D12_MaxQueries; qh.NodeMask = 0; qh.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; QD3D12_CHECK(g_gl.device->CreateQueryHeap(&qh, IID_PPV_ARGS(&g_gl.occlusionQueryHeap))); D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_READBACK; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = sizeof(UINT64) * QD3D12_MaxQueries; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; QD3D12_CHECK( g_gl.device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&g_gl.occlusionReadback))); QD3D12_CHECK(g_gl.occlusionReadback->Map(0, nullptr, (void**)&g_gl.occlusionReadbackCpu)); } // ============================================================ // SECTION 6.5: temporal upscalers / AI denoise helpers // ============================================================ static bool QD3D12_IsDLAAQuality(QD3D12UpscalerQuality quality) { return quality == QD3D12_QUALITY_DLAA; } static float QD3D12_QualityRatio(QD3D12UpscalerQuality quality) { switch (quality) { case QD3D12_QUALITY_DLAA: return 1.0f; case QD3D12_QUALITY_QUALITY: return 0.6666667f; case QD3D12_QUALITY_BALANCED: return 0.58f; case QD3D12_QUALITY_PERFORMANCE: return 0.5f; case QD3D12_QUALITY_ULTRA_PERFORMANCE: return 0.3333333f; case QD3D12_QUALITY_NATIVE: default: return 1.0f; } } #if defined(QD3D12_ENABLE_STREAMLINE) struct QD3D12StreamlineState { bool initialized = false; bool deviceBound = false; bool dlssSupported = false; bool dlssRrSupported = false; sl::ViewportHandle viewport = { 1 }; }; static QD3D12StreamlineState g_qd3d12Sl; typedef HRESULT(WINAPI* QD3D12_PFN_CreateDXGIFactory1)(REFIID riid, void** ppFactory); typedef HRESULT(WINAPI* QD3D12_PFN_D3D12CreateDevice)(IUnknown* pAdapter, D3D_FEATURE_LEVEL minimumFeatureLevel, REFIID riid, void** ppDevice); struct QD3D12StreamlineInterposerState { HMODULE module = nullptr; QD3D12_PFN_CreateDXGIFactory1 createDXGIFactory1 = nullptr; QD3D12_PFN_D3D12CreateDevice d3d12CreateDevice = nullptr; }; static QD3D12StreamlineInterposerState g_qd3d12SlInterposer; static void QD3D12_LoadStreamlineInterposer() { if (g_qd3d12SlInterposer.module) return; HMODULE module = GetModuleHandleA("sl.interposer.dll"); if (!module) module = LoadLibraryA("sl.interposer.dll"); if (!module) { QD3D12_Log("sl.interposer.dll not found; using raw D3D12/DXGI entry points."); return; } g_qd3d12SlInterposer.module = module; g_qd3d12SlInterposer.createDXGIFactory1 = reinterpret_cast(GetProcAddress(module, "CreateDXGIFactory1")); g_qd3d12SlInterposer.d3d12CreateDevice = reinterpret_cast(GetProcAddress(module, "D3D12CreateDevice")); if (!g_qd3d12SlInterposer.createDXGIFactory1 || !g_qd3d12SlInterposer.d3d12CreateDevice) { QD3D12_Log("sl.interposer.dll is loaded but required D3D12/DXGI exports are missing; using raw entry points."); g_qd3d12SlInterposer.createDXGIFactory1 = nullptr; g_qd3d12SlInterposer.d3d12CreateDevice = nullptr; } } static HRESULT QD3D12_CreateDXGIFactory1ForStreamline(REFIID riid, void** ppFactory) { if (g_qd3d12Sl.initialized) { QD3D12_LoadStreamlineInterposer(); if (g_qd3d12SlInterposer.createDXGIFactory1) return g_qd3d12SlInterposer.createDXGIFactory1(riid, ppFactory); } return CreateDXGIFactory1(riid, ppFactory); } static HRESULT QD3D12_D3D12CreateDeviceForStreamline(IUnknown* adapter, D3D_FEATURE_LEVEL featureLevel, REFIID riid, void** ppDevice) { if (g_qd3d12Sl.initialized) { QD3D12_LoadStreamlineInterposer(); if (g_qd3d12SlInterposer.d3d12CreateDevice) return g_qd3d12SlInterposer.d3d12CreateDevice(adapter, featureLevel, riid, ppDevice); } return D3D12CreateDevice(adapter, featureLevel, riid, ppDevice); } static sl::DLSSMode QD3D12_MapDLSSMode(QD3D12UpscalerQuality quality) { switch (quality) { case QD3D12_QUALITY_QUALITY: return sl::DLSSMode::eUltraQuality; case QD3D12_QUALITY_BALANCED: return sl::DLSSMode::eBalanced; case QD3D12_QUALITY_PERFORMANCE: return sl::DLSSMode::eMaxPerformance; case QD3D12_QUALITY_ULTRA_PERFORMANCE: return sl::DLSSMode::eUltraPerformance; case QD3D12_QUALITY_DLAA: return sl::DLSSMode::eDLAA; case QD3D12_QUALITY_NATIVE: default: return sl::DLSSMode::eOff; } } static void QD3D12_InitStreamlineEarly() { if (g_qd3d12Sl.initialized) return; sl::Preferences pref{}; pref.showConsole = true; pref.applicationId = 231313132; // Replace with your NVIDIA-assigned application ID for NGX-backed features like DLSS. pref.engine = sl::EngineType::eCustom; pref.engineVersion = "IceBridge 1.0"; pref.flags |= sl::PreferenceFlags::eUseFrameBasedResourceTagging; sl::Feature features[] = { sl::kFeatureDLSS, sl::kFeatureDLSS_RR }; pref.featuresToLoad = features; pref.numFeaturesToLoad = _countof(features); auto result = slInit(pref); if (result != sl::Result::eOk) { QD3D12_Log("Streamline init failed (%d), DLSS disabled.", int(result)); return; } g_qd3d12Sl.initialized = true; } static void QD3D12_CheckStreamlineFeatureSupport() { g_qd3d12Sl.dlssSupported = false; g_qd3d12Sl.dlssRrSupported = false; if (!g_qd3d12Sl.initialized || !g_gl.device) return; LUID luid = g_gl.device->GetAdapterLuid(); sl::AdapterInfo adapterInfo{}; adapterInfo.deviceLUID = reinterpret_cast(&luid); adapterInfo.deviceLUIDSizeInBytes = sizeof(luid); const sl::Result dlssSupport = slIsFeatureSupported(sl::kFeatureDLSS, adapterInfo); g_qd3d12Sl.dlssSupported = (dlssSupport == sl::Result::eOk); if (!g_qd3d12Sl.dlssSupported) QD3D12_Log("slIsFeatureSupported(DLSS) failed (%d).", int(dlssSupport)); const sl::Result rrSupport = slIsFeatureSupported(sl::kFeatureDLSS_RR, adapterInfo); g_qd3d12Sl.dlssRrSupported = (rrSupport == sl::Result::eOk); if (!g_qd3d12Sl.dlssRrSupported) { QD3D12_Log("slIsFeatureSupported(DLSS_RR) failed (%d); disabling Ray Reconstruction.", int(rrSupport)); g_gl.enableDLSSRayReconstruction = false; } } static void QD3D12_StreamlineOnDeviceCreated() { if (!g_qd3d12Sl.initialized || g_qd3d12Sl.deviceBound || !g_gl.device) return; auto result = slSetD3DDevice(g_gl.device.Get()); if (result != sl::Result::eOk) { QD3D12_Log("slSetD3DDevice failed (%d), DLSS disabled.", int(result)); return; } g_qd3d12Sl.deviceBound = true; QD3D12_CheckStreamlineFeatureSupport(); } static bool QD3D12_WantsDLSSRayReconstruction() { return g_gl.enableDLSSRayReconstruction && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_gl.upscalerQuality != QD3D12_QUALITY_NATIVE && g_qd3d12Sl.deviceBound; } static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window& w) { return !w.isPbuffer && g_gl.cameraState.valid && QD3D12_WantsDLSSRayReconstruction(); } static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window& w) { return QD3D12_CanUseDLSSRayReconstructionForLighting(w) && g_gl.raytracedLightingReadyThisFrame && g_lightingTexture && g_lightingTexture->texture; } static sl::Result QD3D12_SetStreamlineCommonConstants(sl::FrameToken& frameToken) { sl::Constants consts{}; memcpy(&consts.cameraViewToClip, g_gl.cameraState.viewToClip.m, sizeof(float) * 16); memcpy(&consts.clipToCameraView, g_gl.cameraState.clipToView.m, sizeof(float) * 16); memcpy(&consts.clipToPrevClip, g_gl.cameraState.clipToPrevClip.m, sizeof(float) * 16); memcpy(&consts.prevClipToClip, g_gl.cameraState.prevClipToClip.m, sizeof(float) * 16); consts.jitterOffset = { g_gl.jitterX, g_gl.jitterY }; consts.mvecScale = { 1.0f, 1.0f }; // motion vectors are written in PreviousUV - CurrentUV memcpy(&consts.cameraPos, g_gl.cameraState.cameraPos, sizeof(float) * 3); memcpy(&consts.cameraRight, g_gl.cameraState.cameraRight, sizeof(float) * 3); memcpy(&consts.cameraUp, g_gl.cameraState.cameraUp, sizeof(float) * 3); memcpy(&consts.cameraFwd, g_gl.cameraState.cameraForward, sizeof(float) * 3); consts.cameraNear = g_gl.cameraState.nearPlane; consts.cameraFar = g_gl.cameraState.farPlane; consts.cameraFOV = g_gl.cameraState.verticalFovRadians; consts.cameraAspectRatio = g_gl.cameraState.aspectRatio; consts.reset = g_gl.motionHistoryReset ? sl::Boolean::eTrue : sl::Boolean::eFalse; consts.depthInverted = sl::Boolean::eFalse; consts.cameraMotionIncluded = sl::Boolean::eTrue; consts.motionVectors3D = sl::Boolean::eFalse; consts.motionVectorsDilated = sl::Boolean::eFalse; consts.motionVectorsJittered = sl::Boolean::eFalse; return slSetConstants(consts, frameToken, g_qd3d12Sl.viewport); } #else static HRESULT QD3D12_CreateDXGIFactory1ForStreamline(REFIID riid, void** ppFactory) { return CreateDXGIFactory1(riid, ppFactory); } static HRESULT QD3D12_D3D12CreateDeviceForStreamline(IUnknown* adapter, D3D_FEATURE_LEVEL featureLevel, REFIID riid, void** ppDevice) { return D3D12CreateDevice(adapter, featureLevel, riid, ppDevice); } static void QD3D12_InitStreamlineEarly() {} static void QD3D12_StreamlineOnDeviceCreated() {} static bool QD3D12_WantsDLSSRayReconstruction() { return false; } static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window&) { return false; } static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window&) { return false; } #endif #if defined(QD3D12_ENABLE_FFX) struct QD3D12FfxState { bool upscaleContextValid = false; UINT maxRenderWidth = 0; UINT maxRenderHeight = 0; UINT maxOutputWidth = 0; UINT maxOutputHeight = 0; ffx::Context upscaleContext; }; static QD3D12FfxState g_qd3d12Ffx; static void QD3D12_DestroyFfxUpscaleContext() { if (g_qd3d12Ffx.upscaleContextValid) { ffx::DestroyContext(g_qd3d12Ffx.upscaleContext); g_qd3d12Ffx.upscaleContextValid = false; } } static void QD3D12_EnsureFfxUpscaleContext(QD3D12Window& w) { if (!g_gl.device) return; if (g_qd3d12Ffx.upscaleContextValid && g_qd3d12Ffx.maxRenderWidth == w.renderWidth && g_qd3d12Ffx.maxRenderHeight == w.renderHeight && g_qd3d12Ffx.maxOutputWidth == w.width && g_qd3d12Ffx.maxOutputHeight == w.height) { return; } QD3D12_DestroyFfxUpscaleContext(); ffx::CreateBackendDX12Desc backendDesc{}; backendDesc.device = g_gl.device.Get(); ffx::CreateContextDescUpscale createDesc{}; createDesc.maxUpscaleSize = { w.width, w.height }; createDesc.maxRenderSize = { w.renderWidth, w.renderHeight }; createDesc.flags = 0; #ifdef FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE createDesc.flags |= FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE; #endif #ifdef FFX_UPSCALE_ENABLE_AUTO_EXPOSURE createDesc.flags |= FFX_UPSCALE_ENABLE_AUTO_EXPOSURE; #endif ffx::ReturnCode rc = ffx::CreateContext(g_qd3d12Ffx.upscaleContext, nullptr, createDesc, backendDesc); if (rc != ffx::ReturnCode::Ok) { QD3D12_Log("ffx::CreateContext(Upscale) failed (%d), FSR disabled.", int(rc)); return; } g_qd3d12Ffx.upscaleContextValid = true; g_qd3d12Ffx.maxRenderWidth = w.renderWidth; g_qd3d12Ffx.maxRenderHeight = w.renderHeight; g_qd3d12Ffx.maxOutputWidth = w.width; g_qd3d12Ffx.maxOutputHeight = w.height; } #else static void QD3D12_DestroyFfxUpscaleContext() {} static void QD3D12_EnsureFfxUpscaleContext(QD3D12Window&) {} #endif static void QD3D12_SelectRenderResolution(QD3D12Window& w, UINT outputWidth, UINT outputHeight) { w.width = outputWidth; w.height = outputHeight; w.renderWidth = outputWidth; w.renderHeight = outputHeight; if (g_gl.upscalerBackend == QD3D12_UPSCALER_NONE || g_gl.upscalerQuality == QD3D12_QUALITY_NATIVE) return; // DLAA is DLSS at native resolution: keep the internal render target at the // output size, but do not treat it like QD3D12_QUALITY_NATIVE because the // Streamline DLSS evaluation still needs to run later in QD3D12_RunUpscalerOrBlit(). if (QD3D12_IsDLAAQuality(g_gl.upscalerQuality)) return; #if defined(QD3D12_ENABLE_STREAMLINE) if (g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound) { if (QD3D12_WantsDLSSRayReconstruction()) { sl::DLSSDOptions rrOptions{}; rrOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); rrOptions.outputWidth = outputWidth; rrOptions.outputHeight = outputHeight; rrOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction rrOptions.preExposure = 1.0f; rrOptions.exposureScale = 1.0f; rrOptions.colorBuffersHDR = sl::Boolean::eTrue; rrOptions.normalRoughnessMode = sl::DLSSDNormalRoughnessMode::ePacked; sl::DLSSDOptimalSettings rrOptimal{}; const sl::Result rrOptimalResult = slDLSSDGetOptimalSettings(rrOptions, rrOptimal); if (rrOptimalResult == sl::Result::eOk) { w.renderWidth = rrOptimal.optimalRenderWidth; w.renderHeight = rrOptimal.optimalRenderHeight; return; } QD3D12_Log("slDLSSDGetOptimalSettings failed (%d), falling back to DLSS SR sizing.", int(rrOptimalResult)); } sl::DLSSOptions options{}; options.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); options.outputWidth = outputWidth; options.outputHeight = outputHeight; sl::DLSSOptimalSettings optimal{}; const sl::Result optimalResult = slDLSSGetOptimalSettings(options, optimal); if (optimalResult == sl::Result::eOk) { w.renderWidth = optimal.optimalRenderWidth; w.renderHeight = optimal.optimalRenderHeight; return; } } #endif const float ratio = QD3D12_QualityRatio(g_gl.upscalerQuality); w.renderWidth = std::max(1, (UINT)std::lround((double)outputWidth * ratio)); w.renderHeight = std::max(1, (UINT)std::lround((double)outputHeight * ratio)); } static void QD3D12_SetPostConstants( ID3D12GraphicsCommandList* cl, float sharpness, float taaHistoryWeight, float taaReset, float toneMapExposure = 1.0f, float toneMapWhiteScale = 1.0f, float toneMapBrightness = 1.0f, bool applySceneFog = false) { if (!cl) return; float constants[QD3D12_PostConstantDwords] = {}; constants[0] = sharpness; constants[1] = taaHistoryWeight; constants[2] = taaReset; constants[4] = toneMapExposure; constants[5] = toneMapWhiteScale; constants[6] = toneMapBrightness; if (applySceneFog && g_gl.sceneFogValidThisFrame) { constants[8] = 1.0f; constants[9] = g_gl.sceneFogMode; constants[10] = g_gl.sceneFogDensity; constants[11] = g_gl.sceneFogStart; constants[12] = g_gl.sceneFogEnd; constants[13] = g_gl.sceneFogCameraValid; constants[16] = g_gl.sceneFogColor[0]; constants[17] = g_gl.sceneFogColor[1]; constants[18] = g_gl.sceneFogColor[2]; constants[19] = g_gl.sceneFogColor[3]; constants[20] = g_gl.sceneFogCameraWorldPos[0]; constants[21] = g_gl.sceneFogCameraWorldPos[1]; constants[22] = g_gl.sceneFogCameraWorldPos[2]; } cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0); } static void QD3D12_BindPostFogResources(ID3D12GraphicsCommandList* cl, QD3D12Window& w, bool applySceneFog) { if (!cl || !applySceneFog || !g_gl.sceneFogValidThisFrame) return; if (!w.positionBuffers[w.frameIndex] || !w.depthBuffer) return; if (w.positionSrvIndex[w.frameIndex] == UINT_MAX || w.depthSrvIndex == UINT_MAX) return; cl->SetGraphicsRootDescriptorTable(9, w.positionSrvGpu[w.frameIndex]); cl->SetGraphicsRootDescriptorTable(10, w.depthSrvGpu); } static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, ID3D12PipelineState* pso, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_CPU_DESCRIPTOR_HANDLE outputRtv, const D3D12_VIEWPORT& viewport, const D3D12_RECT& scissor, float sharpnessOverride = -1.0f, bool applySceneFog = false, QD3D12Window* fogWindow = nullptr) { if (!cl || !pso) return; ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); const float passSharpness = (sharpnessOverride >= 0.0f) ? sharpnessOverride : g_gl.upscalerSharpness; QD3D12_SetPostConstants(cl, passSharpness, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, applySceneFog); cl->SetPipelineState(pso); cl->OMSetRenderTargets(1, &outputRtv, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cl->SetGraphicsRootDescriptorTable(0, inputSrv); if (fogWindow) QD3D12_BindPostFogResources(cl, *fogWindow, applySceneFog); cl->DrawInstanced(3, 1, 0, 0); } static bool QD3D12_GenerateToneMapMaxBrightness(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE hdrInputSrv) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); const UINT frame = w.frameIndex; if (!cl || !g_gl.postToneMapTileMaxPSO || !g_gl.postToneMapFinalMaxPSO) return false; if (!w.toneMapTileMaxBuffers[frame] || !w.toneMapSceneMaxBuffers[frame]) return false; QD3D12_TransitionResource( cl, w.toneMapTileMaxBuffers[frame].Get(), w.toneMapTileMaxState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_VIEWPORT tileViewport{}; tileViewport.TopLeftX = 0.0f; tileViewport.TopLeftY = 0.0f; tileViewport.Width = (float)QD3D12_ToneMapTileDim; tileViewport.Height = (float)QD3D12_ToneMapTileDim; tileViewport.MinDepth = 0.0f; tileViewport.MaxDepth = 1.0f; D3D12_RECT tileScissor{}; tileScissor.left = 0; tileScissor.top = 0; tileScissor.right = (LONG)QD3D12_ToneMapTileDim; tileScissor.bottom = (LONG)QD3D12_ToneMapTileDim; QD3D12_PostFullscreenPass( cl, g_gl.postToneMapTileMaxPSO.Get(), hdrInputSrv, CurrentToneMapTileMaxRTV(), tileViewport, tileScissor, 0.0f); QD3D12_TransitionResource( cl, w.toneMapTileMaxBuffers[frame].Get(), w.toneMapTileMaxState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource( cl, w.toneMapSceneMaxBuffers[frame].Get(), w.toneMapSceneMaxState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_VIEWPORT onePixelViewport{}; onePixelViewport.TopLeftX = 0.0f; onePixelViewport.TopLeftY = 0.0f; onePixelViewport.Width = 1.0f; onePixelViewport.Height = 1.0f; onePixelViewport.MinDepth = 0.0f; onePixelViewport.MaxDepth = 1.0f; D3D12_RECT onePixelScissor{}; onePixelScissor.left = 0; onePixelScissor.top = 0; onePixelScissor.right = 1; onePixelScissor.bottom = 1; QD3D12_PostFullscreenPass( cl, g_gl.postToneMapFinalMaxPSO.Get(), w.toneMapTileMaxSrvGpu[frame], CurrentToneMapSceneMaxRTV(), onePixelViewport, onePixelScissor, 0.0f); QD3D12_TransitionResource( cl, w.toneMapSceneMaxBuffers[frame].Get(), w.toneMapSceneMaxState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); return true; } static bool QD3D12_ToneMapFullscreenPass( QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE hdrInputSrv, D3D12_CPU_DESCRIPTOR_HANDLE outputRtv, const D3D12_VIEWPORT& viewport, const D3D12_RECT& scissor, float sharpnessOverride = -1.0f, bool applySceneFog = true) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); const UINT frame = w.frameIndex; if (!cl || !g_gl.postToneMapPSO) return false; if (!QD3D12_GenerateToneMapMaxBrightness(w, hdrInputSrv)) return false; ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); const float passSharpness = (sharpnessOverride >= 0.0f) ? sharpnessOverride : g_gl.upscalerSharpness; QD3D12_SetPostConstants(cl, passSharpness, 0.0f, 1.0f, 1.0f, 1.0f, g_gl.toneMapBrightness, applySceneFog); cl->SetPipelineState(g_gl.postToneMapPSO.Get()); cl->OMSetRenderTargets(1, &outputRtv, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cl->SetGraphicsRootDescriptorTable(0, hdrInputSrv); cl->SetGraphicsRootDescriptorTable(7, w.toneMapSceneMaxSrvGpu[frame]); QD3D12_BindPostFogResources(cl, w, applySceneFog); cl->DrawInstanced(3, 1, 0, 0); return true; } 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(); if (!cl) return nullptr; ID3D12Resource* output = w.slOutputBuffers[w.frameIndex].Get(); if (!output) { QD3D12_Log("Streamline output texture is missing for frame %u.", w.frameIndex); return nullptr; } QD3D12_TransitionResource( cl, output, w.slOutputState[w.frameIndex], D3D12_RESOURCE_STATE_UNORDERED_ACCESS); return output; } static bool QD3D12_CopyStreamlineOutputToBackBuffer( QD3D12Window& w, const D3D12_VIEWPORT& outputViewport, const D3D12_RECT& outputScissor, bool toneMapInput) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !w.slOutputBuffers[w.frameIndex] || !g_gl.postCopyPSO) return false; QD3D12_TransitionResource( cl, w.slOutputBuffers[w.frameIndex].Get(), w.slOutputState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); return QD3D12_FinalBlitToBackBuffer(w, w.slOutputSrvGpu[w.frameIndex], outputViewport, outputScissor, toneMapInput); } static bool QD3D12_ExternalTemporalUpscalerActive(const QD3D12Window& w) { if (w.isPbuffer) return false; #if defined(QD3D12_ENABLE_STREAMLINE) if (g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && g_gl.upscalerQuality != QD3D12_QUALITY_NATIVE) { return true; } #endif #if defined(QD3D12_ENABLE_FFX) if (g_gl.upscalerBackend == QD3D12_UPSCALER_FSR && !QD3D12_IsDLAAQuality(g_gl.upscalerQuality) && g_gl.upscalerQuality != QD3D12_QUALITY_NATIVE) { return true; } #endif return false; } static bool QD3D12_ShouldRunInternalTAA(const QD3D12Window& w) { // DLSS/DLSS-RR/FSR are already temporal. The internal TAA pass replaces MSAA // for the native/no-upscaler path and runs after lighting but before 2D. return g_gl.enableInternalTAA && !w.isPbuffer && !QD3D12_ExternalTemporalUpscalerActive(w); } static bool QD3D12_FinalBlitToBackBuffer( QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, const D3D12_VIEWPORT& outputViewport, const D3D12_RECT& outputScissor, bool toneMapInput) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !g_gl.postCopyPSO) return false; QD3D12_TransitionResource( cl, w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; cl->ClearRenderTargetView(CurrentBackBufferRTV(), clearColor, 0, nullptr); if (toneMapInput) { // HDR inputs get reduced to a brightest-pixel white point, then tone mapped. // If that path is unavailable, fall through to the old copy/sharpen path. if (QD3D12_ToneMapFullscreenPass(w, inputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor)) return true; } ID3D12PipelineState* finalPso = (g_gl.upscalerSharpness > 0.0001f && g_gl.postSharpenPSO) ? g_gl.postSharpenPSO.Get() : g_gl.postCopyPSO.Get(); // This is the only final sharpen point. All temporal work (internal TAA, // DLSS/DLSS-RR, or FSR) has already completed before this fullscreen pass. QD3D12_PostFullscreenPass( cl, finalPso, inputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor, -1.0f, true, &w); return true; } static bool QD3D12_RunInternalTAA(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_GPU_DESCRIPTOR_HANDLE& outputSrv) { outputSrv = inputSrv; ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); const UINT frame = w.frameIndex; const UINT historyFrame = frame ^ 1u; if (!cl || !g_gl.postTaaPSO || !w.taaBuffers[frame] || !w.velocityBuffers[frame]) return false; if (!QD3D12_ShouldRunInternalTAA(w)) return false; QD3D12_TransitionResource(cl, w.taaBuffers[frame].Get(), w.taaBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.velocityBuffers[frame].Get(), w.velocityBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); const bool resetHistory = g_gl.motionHistoryReset || !w.taaHistoryValid[historyFrame] || !w.taaBuffers[historyFrame]; D3D12_GPU_DESCRIPTOR_HANDLE historySrv = inputSrv; if (!resetHistory) { QD3D12_TransitionResource(cl, w.taaBuffers[historyFrame].Get(), w.taaBufferState[historyFrame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); historySrv = w.taaSrvGpu[historyFrame]; } 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; ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.90f, resetHistory ? 1.0f : 0.0f); cl->SetPipelineState(g_gl.postTaaPSO.Get()); D3D12_CPU_DESCRIPTOR_HANDLE rtv = CurrentTaaOutputRTV(); cl->OMSetRenderTargets(1, &rtv, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cl->SetGraphicsRootDescriptorTable(0, inputSrv); cl->SetGraphicsRootDescriptorTable(7, historySrv); cl->SetGraphicsRootDescriptorTable(8, w.velocitySrvGpu[frame]); cl->DrawInstanced(3, 1, 0, 0); QD3D12_TransitionResource(cl, w.taaBuffers[frame].Get(), w.taaBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); w.taaHistoryValid[frame] = true; outputSrv = w.taaSrvGpu[frame]; return true; } static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w) { QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); w.frames[w.frameIndex].fenceValue = signalValue; if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } QD3D12_CHECK(w.frames[w.frameIndex].cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(w.frames[w.frameIndex].cmdAlloc.Get(), nullptr)); g_gl.frameOpen = true; g_gl.frameOwner = &w; } static void QD3D12_RunRayAIDenoiseIfEnabled(ID3D12GraphicsCommandList* cl, QD3D12Window& w, ID3D12Resource* lightingResource) { (void)cl; (void)w; (void)lightingResource; if (!g_gl.enableRayAIDenoise) return; // DLSS Ray Reconstruction is evaluated in QD3D12_RunUpscalerOrBlit(), because // Streamline wants the noisy lighting input, depth, motion vectors, albedo, and // normal/roughness tagged together with the final scaling output. Do not run a // second temporal shim denoiser here. if (QD3D12_WantsDLSSRayReconstruction()) return; } static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; QD3D12_UpdateCameraInfoFromCurrentMatrices(); const bool haveTemporalCameraInputs = (!w.isPbuffer) && g_gl.cameraState.valid; const bool useLightingUpscaleInput = (!w.isPbuffer) && QD3D12_UseLightingTextureAsUpscaleInput(w); ID3D12Resource* upscaleInputResource = w.sceneColorBuffers[w.frameIndex].Get(); D3D12_GPU_DESCRIPTOR_HANDLE upscaleInputSrv = w.sceneColorSrvGpu[w.frameIndex]; if (useLightingUpscaleInput && g_lightingTexture && g_lightingTexture->texture) { upscaleInputResource = g_lightingTexture->texture.Get(); upscaleInputSrv = g_lightingTexture->srvGpu; } D3D12_VIEWPORT outputViewport{}; outputViewport.TopLeftX = 0.0f; outputViewport.TopLeftY = 0.0f; outputViewport.Width = (float)w.width; outputViewport.Height = (float)w.height; outputViewport.MinDepth = 0.0f; outputViewport.MaxDepth = 1.0f; D3D12_RECT outputScissor{}; outputScissor.left = 0; outputScissor.top = 0; outputScissor.right = (LONG)w.width; outputScissor.bottom = (LONG)w.height; #if defined(QD3D12_ENABLE_STREAMLINE) if (haveTemporalCameraInputs && useLightingUpscaleInput && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { sl::FrameToken* frameToken = nullptr; uint32_t frameIndex = (uint32_t)g_gl.frameSerial; const sl::Result frameResult = slGetNewFrameToken(frameToken, &frameIndex); if (frameResult == sl::Result::eOk && frameToken) { sl::Extent renderExtent{}; renderExtent.left = 0; renderExtent.top = 0; renderExtent.width = w.renderWidth; renderExtent.height = w.renderHeight; sl::Extent outputExtent{}; outputExtent.left = 0; outputExtent.top = 0; outputExtent.width = w.width; outputExtent.height = w.height; ID3D12Resource* streamlineOutputResource = QD3D12_PrepareStreamlineOutputForWrite(w); sl::Resource colorIn = { sl::ResourceType::eTex2d, upscaleInputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource colorOut = { sl::ResourceType::eTex2d, streamlineOutputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_UNORDERED_ACCESS) }; sl::Resource depth = { sl::ResourceType::eTex2d, w.depthBuffer.Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource mvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource specularMvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource diffuseAlbedo = { sl::ResourceType::eTex2d, w.sceneColorBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource specularAlbedo = { sl::ResourceType::eTex2d, w.specularBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource normalRoughness = { sl::ResourceType::eTex2d, w.normalBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::ResourceTag tags[] = { sl::ResourceTag{ &colorIn, sl::kBufferTypeScalingInputColor, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &colorOut, sl::kBufferTypeScalingOutputColor, sl::ResourceLifecycle::eOnlyValidNow, &outputExtent }, sl::ResourceTag{ &depth, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent, &renderExtent }, sl::ResourceTag{ &mvec, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &specularMvec, sl::kBufferTypeSpecularMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &diffuseAlbedo, sl::kBufferTypeAlbedo, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &specularAlbedo, sl::kBufferTypeSpecularAlbedo, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &normalRoughness, sl::kBufferTypeNormalRoughness, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent } }; slSetTagForFrame(*frameToken, g_qd3d12Sl.viewport, tags, _countof(tags), cl); sl::DLSSOptions dlssOptions{}; dlssOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); dlssOptions.outputWidth = w.width; dlssOptions.outputHeight = w.height; dlssOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction dlssOptions.colorBuffersHDR = sl::Boolean::eTrue; dlssOptions.useAutoExposure = sl::Boolean::eFalse; dlssOptions.alphaUpscalingEnabled = sl::Boolean::eFalse; const sl::Result dlssOptionsResult = slDLSSSetOptions(g_qd3d12Sl.viewport, dlssOptions); if (dlssOptionsResult != sl::Result::eOk) { QD3D12_Log("slDLSSSetOptions for DLSS_RR failed (%d), falling back.", int(dlssOptionsResult)); } else { sl::DLSSDOptions rrOptions{}; rrOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); rrOptions.outputWidth = w.width; rrOptions.outputHeight = w.height; rrOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction rrOptions.preExposure = 1.0f; rrOptions.exposureScale = 1.0f; rrOptions.colorBuffersHDR = sl::Boolean::eTrue; rrOptions.normalRoughnessMode = sl::DLSSDNormalRoughnessMode::ePacked; rrOptions.alphaUpscalingEnabled = sl::Boolean::eFalse; Mat4 identity = Mat4::Identity(); memcpy(&rrOptions.worldToCameraView, identity.m, sizeof(float) * 16); memcpy(&rrOptions.cameraViewToWorld, identity.m, sizeof(float) * 16); const sl::Result rrOptionsResult = slDLSSDSetOptions(g_qd3d12Sl.viewport, rrOptions); if (rrOptionsResult != sl::Result::eOk) { QD3D12_Log("slDLSSDSetOptions failed (%d), falling back.", int(rrOptionsResult)); } else { const sl::Result constResult = QD3D12_SetStreamlineCommonConstants(*frameToken); if (constResult != sl::Result::eOk) { QD3D12_Log("slSetConstants for DLSS_RR failed (%d), falling back.", int(constResult)); } else { const sl::BaseStructure* inputs[] = { &g_qd3d12Sl.viewport }; const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS_RR, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, true)) return; QD3D12_Log("DLSS_RR succeeded but copying Streamline output to backbuffer failed; falling back."); } else { QD3D12_Log("slEvaluateFeature(DLSS_RR) failed (%d), falling back.", int(evalResult)); } } } } } else if (frameResult != sl::Result::eOk) { QD3D12_Log("slGetNewFrameToken for DLSS_RR failed (%d), falling back.", int(frameResult)); } } if (haveTemporalCameraInputs && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { sl::FrameToken* frameToken = nullptr; uint32_t frameIndex = (uint32_t)g_gl.frameSerial; const sl::Result frameResult = slGetNewFrameToken(frameToken, &frameIndex); if (frameResult == sl::Result::eOk && frameToken) { sl::Extent renderExtent{}; renderExtent.left = 0; renderExtent.top = 0; renderExtent.width = w.renderWidth; renderExtent.height = w.renderHeight; sl::Extent outputExtent{}; outputExtent.left = 0; outputExtent.top = 0; outputExtent.width = w.width; outputExtent.height = w.height; ID3D12Resource* streamlineOutputResource = QD3D12_PrepareStreamlineOutputForWrite(w); sl::Resource colorIn = { sl::ResourceType::eTex2d, upscaleInputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource colorOut = { sl::ResourceType::eTex2d, streamlineOutputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_UNORDERED_ACCESS) }; sl::Resource depth = { sl::ResourceType::eTex2d, w.depthBuffer.Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::Resource mvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; sl::ResourceTag tags[] = { sl::ResourceTag{ &colorIn, sl::kBufferTypeScalingInputColor, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &colorOut, sl::kBufferTypeScalingOutputColor, sl::ResourceLifecycle::eOnlyValidNow, &outputExtent }, sl::ResourceTag{ &depth, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent, &renderExtent }, sl::ResourceTag{ &mvec, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent } }; slSetTagForFrame(*frameToken, g_qd3d12Sl.viewport, tags, _countof(tags), cl); sl::DLSSOptions options{}; options.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); options.outputWidth = w.width; options.outputHeight = w.height; options.sharpness = 0.0f; // final sharpening happens after temporal reconstruction options.colorBuffersHDR = useLightingUpscaleInput ? sl::Boolean::eTrue : sl::Boolean::eFalse; options.useAutoExposure = useLightingUpscaleInput ? sl::Boolean::eFalse : sl::Boolean::eTrue; options.alphaUpscalingEnabled = sl::Boolean::eFalse; slDLSSSetOptions(g_qd3d12Sl.viewport, options); const sl::Result constResult = QD3D12_SetStreamlineCommonConstants(*frameToken); if (constResult != sl::Result::eOk) { QD3D12_Log("slSetConstants for DLSS failed (%d), falling back.", int(constResult)); } else { const sl::BaseStructure* inputs[] = { &g_qd3d12Sl.viewport }; const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, useLightingUpscaleInput)) return; QD3D12_Log("DLSS succeeded but copying Streamline output to backbuffer failed; falling back."); } else { QD3D12_Log("slEvaluateFeature(DLSS) failed (%d), falling back.", int(evalResult)); } } } } #endif #if defined(QD3D12_ENABLE_FFX) if (haveTemporalCameraInputs && g_gl.upscalerBackend == QD3D12_UPSCALER_FSR && !QD3D12_IsDLAAQuality(g_gl.upscalerQuality)) { QD3D12_EnsureFfxUpscaleContext(w); if (g_qd3d12Ffx.upscaleContextValid) { ffx::DispatchDescUpscale dispatch{}; dispatch.commandList = cl; dispatch.color = ffxApiGetResourceDX12(w.sceneColorBuffers[w.frameIndex].Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.depth = ffxApiGetResourceDX12(w.depthBuffer.Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.motionVectors = ffxApiGetResourceDX12(w.velocityBuffers[w.frameIndex].Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); ID3D12Resource* ffxOutputResource = QD3D12_PrepareStreamlineOutputForWrite(w); if (!ffxOutputResource) return; dispatch.output = ffxApiGetResourceDX12(ffxOutputResource, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.jitterOffset.x = -g_gl.jitterX; dispatch.jitterOffset.y = -g_gl.jitterY; dispatch.motionVectorScale.x = (float)w.renderWidth; dispatch.motionVectorScale.y = (float)w.renderHeight; dispatch.renderSize.width = w.renderWidth; dispatch.renderSize.height = w.renderHeight; dispatch.upscaleSize.width = w.width; dispatch.upscaleSize.height = w.height; dispatch.enableSharpening = false; dispatch.sharpness = 0.0f; dispatch.frameTimeDelta = 16.6667f; dispatch.preExposure = 1.0f; dispatch.reset = g_gl.motionHistoryReset; dispatch.cameraNear = g_gl.cameraState.nearPlane; dispatch.cameraFar = g_gl.cameraState.farPlane; dispatch.cameraFovAngleVertical = g_gl.cameraState.verticalFovRadians; dispatch.viewSpaceToMetersFactor = 1.0f; dispatch.flags = 0; if (ffx::Dispatch(g_qd3d12Ffx.upscaleContext, dispatch) == ffx::ReturnCode::Ok) { if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, false)) return; } } } #endif D3D12_GPU_DESCRIPTOR_HANDLE finalInputSrv = upscaleInputSrv; QD3D12_RunInternalTAA(w, upscaleInputSrv, finalInputSrv); QD3D12_FinalBlitToBackBuffer(w, finalInputSrv, outputViewport, outputScissor, useLightingUpscaleInput); } static bool QD3D12_FormatSupportsSampleCount(DXGI_FORMAT format, UINT sampleCount) { if (!g_gl.device || sampleCount <= 1) return true; D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS levels{}; levels.Format = format; levels.SampleCount = sampleCount; levels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE; levels.NumQualityLevels = 0; if (FAILED(g_gl.device->CheckFeatureSupport( D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &levels, sizeof(levels)))) { return false; } return levels.NumQualityLevels > 0; } static bool QD3D12_FormatSupportsMsaaResolve(DXGI_FORMAT format) { if (!g_gl.device) return false; D3D12_FEATURE_DATA_FORMAT_SUPPORT support{}; support.Format = format; if (FAILED(g_gl.device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)))) return false; return (support.Support1 & D3D12_FORMAT_SUPPORT1_MULTISAMPLE_RESOLVE) != 0; } static bool QD3D12_CanUseGBufferSampleCount(UINT sampleCount) { if (sampleCount <= 1) return true; return // Scene color/albedo is color-like, so the hardware color resolve is fine. QD3D12_FormatSupportsSampleCount(QD3D12_SceneColorFormat, sampleCount) && QD3D12_FormatSupportsMsaaResolve(QD3D12_SceneColorFormat) && // Normals, world positions, material flags, and motion vectors are not // hardware-resolved. They only need to support MSAA render-target + SRV use; // QD3D12_PointResolveMsaaGBufferToSingleSample() point-loads one sample. QD3D12_FormatSupportsSampleCount(DXGI_FORMAT_R16G16B16A16_FLOAT, sampleCount) && QD3D12_FormatSupportsSampleCount(QD3D12_VelocityFormat, sampleCount) && QD3D12_FormatSupportsSampleCount(QD3D12_SpecularAlbedoFormat, sampleCount) && QD3D12_FormatSupportsSampleCount(QD3D12_DepthDsvFormat, sampleCount); } static void QD3D12_SelectGBufferSampleCount() { g_qd3d12GBufferSampleCount = 1; QD3D12_Log("QD3D12 G-buffer MSAA removed: single-sample render targets active."); } static void QD3D12_CreateDevice() { QD3D12_InitStreamlineEarly(); #if defined(_DEBUG) //{ // ComPtr debug; // if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug)))) // debug->EnableDebugLayer(); //} #endif QD3D12_CHECK(QD3D12_CreateDXGIFactory1ForStreamline(IID_PPV_ARGS(&g_gl.factory))); QD3D12_CHECK(QD3D12_D3D12CreateDeviceForStreamline(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_gl.device))); QD3D12_SelectGBufferSampleCount(); D3D12_COMMAND_QUEUE_DESC qd{}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; QD3D12_CHECK(g_gl.device->CreateCommandQueue(&qd, IID_PPV_ARGS(&g_gl.queue))); QD3D12_StreamlineOnDeviceCreated(); } static void QD3D12_CreateSwapChainForWindow(QD3D12Window& w) { DXGI_SWAP_CHAIN_DESC1 sd{}; sd.BufferCount = QD3D12_FrameCount; sd.Width = w.width; sd.Height = w.height; sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; sd.SampleDesc.Count = 1; sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; ComPtr sc1; QD3D12_CHECK(g_gl.factory->CreateSwapChainForHwnd( g_gl.queue.Get(), w.hwnd, &sd, nullptr, nullptr, &sc1)); QD3D12_CHECK(sc1.As(&w.swapChain)); w.frameIndex = w.swapChain->GetCurrentBackBufferIndex(); } static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) { D3D12_DESCRIPTOR_HEAP_DESC hd{}; hd.NumDescriptors = QD3D12_FrameCount * QD3D12_RTV_GROUP_COUNT; hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; QD3D12_CHECK(g_gl.device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&w.rtvHeap))); w.rtvStride = g_gl.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); auto CreateTexture2D = [&](ComPtr& outRes, D3D12_RESOURCE_STATES& outState, DXGI_FORMAT format, const float clearColor[4], bool useOptimizedClear, UINT texWidth, UINT texHeight, UINT sampleCount, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES initialState, D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle, bool createRtv) { D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Width = texWidth; rd.Height = texHeight; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.Format = format; rd.SampleDesc.Count = std::max(1u, sampleCount); rd.SampleDesc.Quality = 0; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; rd.Flags = flags; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; D3D12_CLEAR_VALUE clear{}; const D3D12_CLEAR_VALUE* clearPtr = nullptr; if (useOptimizedClear && (flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)) { clear.Format = format; clear.Color[0] = clearColor[0]; clear.Color[1] = clearColor[1]; clear.Color[2] = clearColor[2]; clear.Color[3] = clearColor[3]; clearPtr = &clear; } QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, initialState, clearPtr, IID_PPV_ARGS(&outRes))); outState = initialState; if (createRtv) g_gl.device->CreateRenderTargetView(outRes.Get(), nullptr, rtvHandle); }; const float colorClear[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; 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 specularClear[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; const bool useMsaa = QD3D12_GBufferMsaaEnabled(); const UINT msaaSamples = QD3D12_GBufferSampleCount(); for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (useMsaa) { CreateTexture2D( w.sceneColorMsaaBuffers[i], w.sceneColorMsaaState[i], QD3D12_SceneColorFormat, colorClear, false, w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RENDER, i), true); CreateTexture2D( w.sceneColorBuffers[i], w.sceneColorState[i], QD3D12_SceneColorFormat, colorClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RESOLVED, i), true); } else { CreateTexture2D( w.sceneColorBuffers[i], w.sceneColorState[i], QD3D12_SceneColorFormat, colorClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RENDER, i), true); g_gl.device->CreateRenderTargetView( w.sceneColorBuffers[i].Get(), nullptr, QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RESOLVED, i)); } } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (useMsaa) { CreateTexture2D(w.normalMsaaBuffers[i], w.normalMsaaState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, normalClear, true, w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_NORMAL_RENDER, i), true); CreateTexture2D(w.normalBuffers[i], w.normalBufferState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, normalClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_NORMAL_RESOLVED, i), true); } else { CreateTexture2D(w.normalBuffers[i], w.normalBufferState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, normalClear, true, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_NORMAL_RENDER, i), true); } } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (useMsaa) { CreateTexture2D(w.positionMsaaBuffers[i], w.positionMsaaState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, positionClear, true, w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_POSITION_RENDER, i), true); CreateTexture2D(w.positionBuffers[i], w.positionBufferState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, positionClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_POSITION_RESOLVED, i), true); } else { CreateTexture2D(w.positionBuffers[i], w.positionBufferState[i], DXGI_FORMAT_R16G16B16A16_FLOAT, positionClear, true, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_POSITION_RENDER, i), true); } } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (useMsaa) { CreateTexture2D(w.velocityMsaaBuffers[i], w.velocityMsaaState[i], QD3D12_VelocityFormat, velocityClear, true, w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_VELOCITY_RENDER, i), true); CreateTexture2D(w.velocityBuffers[i], w.velocityBufferState[i], QD3D12_VelocityFormat, velocityClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_VELOCITY_RESOLVED, i), true); } else { CreateTexture2D(w.velocityBuffers[i], w.velocityBufferState[i], QD3D12_VelocityFormat, velocityClear, true, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_VELOCITY_RENDER, i), true); } } 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) { if (useMsaa) { CreateTexture2D(w.specularMsaaBuffers[i], w.specularMsaaState[i], QD3D12_SpecularAlbedoFormat, specularClear, true, w.renderWidth, w.renderHeight, msaaSamples, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_SPECULAR_RENDER, i), true); CreateTexture2D(w.specularBuffers[i], w.specularBufferState[i], QD3D12_SpecularAlbedoFormat, specularClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_SPECULAR_RESOLVED, i), true); } else { CreateTexture2D(w.specularBuffers[i], w.specularBufferState[i], QD3D12_SpecularAlbedoFormat, specularClear, true, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, QD3D12_RtvAt(w, QD3D12_RTV_SPECULAR_RENDER, i), true); } } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { D3D12_CPU_DESCRIPTOR_HANDLE backBufferRtv = QD3D12_RtvAt(w, QD3D12_RTV_BACKBUFFER, i); if (w.swapChain) { QD3D12_CHECK(w.swapChain->GetBuffer(i, IID_PPV_ARGS(&w.backBuffers[i]))); g_gl.device->CreateRenderTargetView(w.backBuffers[i].Get(), nullptr, backBufferRtv); w.backBufferState[i] = D3D12_RESOURCE_STATE_PRESENT; } else { // Offscreen pbuffer backbuffers are ordinary render-target textures. // EndFrame will transition them to PRESENT/COMMON after the resolve/blit. CreateTexture2D(w.backBuffers[i], w.backBufferState[i], QD3D12_SceneColorFormat, colorClear, false, w.width, w.height, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET, backBufferRtv, true); } } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { CreateTexture2D( w.slOutputBuffers[i], w.slOutputState[i], QD3D12_StreamlineOutputFormat, colorClear, false, w.width, w.height, 1, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, {}, false); } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { CreateTexture2D( w.taaBuffers[i], w.taaBufferState[i], QD3D12_StreamlineOutputFormat, colorClear, false, w.renderWidth, w.renderHeight, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_TAA_OUTPUT, i), true); w.taaHistoryValid[i] = false; } for (UINT i = 0; i < QD3D12_FrameCount; ++i) { CreateTexture2D( w.toneMapTileMaxBuffers[i], w.toneMapTileMaxState[i], QD3D12_StreamlineOutputFormat, colorClear, false, QD3D12_ToneMapTileDim, QD3D12_ToneMapTileDim, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_TILE_MAX, i), true); CreateTexture2D( w.toneMapSceneMaxBuffers[i], w.toneMapSceneMaxState[i], QD3D12_StreamlineOutputFormat, colorClear, false, 1, 1, 1, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_SCENE_MAX, i), true); } auto CreateTextureSrv = [&](ID3D12Resource* res, DXGI_FORMAT format, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& cpu, D3D12_GPU_DESCRIPTOR_HANDLE& gpu) { if (!res) return; if (srvIndex == UINT_MAX) { srvIndex = g_gl.nextSrvIndex++; cpu = QD3D12_SrvCpu(srvIndex); gpu = QD3D12_SrvGpu(srvIndex); } D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; sd.Format = format; sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; sd.Texture2D.MipLevels = 1; g_gl.device->CreateShaderResourceView(res, &sd, cpu); }; auto CreateTextureMsaaSrv = [&](ID3D12Resource* res, DXGI_FORMAT format, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& cpu, D3D12_GPU_DESCRIPTOR_HANDLE& gpu) { if (!res) return; if (srvIndex == UINT_MAX) { srvIndex = g_gl.nextSrvIndex++; cpu = QD3D12_SrvCpu(srvIndex); gpu = QD3D12_SrvGpu(srvIndex); } D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; sd.Format = format; sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; g_gl.device->CreateShaderResourceView(res, &sd, cpu); }; for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.sceneColorBuffers[i].Get(), QD3D12_SceneColorFormat, w.sceneColorSrvIndex[i], w.sceneColorSrvCpu[i], w.sceneColorSrvGpu[i]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.normalBuffers[i].Get(), DXGI_FORMAT_R16G16B16A16_FLOAT, w.normalSrvIndex[i], w.normalSrvCpu[i], w.normalSrvGpu[i]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.positionBuffers[i].Get(), DXGI_FORMAT_R16G16B16A16_FLOAT, w.positionSrvIndex[i], w.positionSrvCpu[i], w.positionSrvGpu[i]); 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.specularBuffers[i].Get(), QD3D12_SpecularAlbedoFormat, w.specularSrvIndex[i], w.specularSrvCpu[i], w.specularSrvGpu[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]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.taaBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.taaSrvIndex[i], w.taaSrvCpu[i], w.taaSrvGpu[i]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.toneMapTileMaxBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.toneMapTileMaxSrvIndex[i], w.toneMapTileMaxSrvCpu[i], w.toneMapTileMaxSrvGpu[i]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.toneMapSceneMaxBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.toneMapSceneMaxSrvIndex[i], w.toneMapSceneMaxSrvCpu[i], w.toneMapSceneMaxSrvGpu[i]); if (useMsaa) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureMsaaSrv(w.normalMsaaBuffers[i].Get(), DXGI_FORMAT_R16G16B16A16_FLOAT, w.normalMsaaSrvIndex[i], w.normalMsaaSrvCpu[i], w.normalMsaaSrvGpu[i]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureMsaaSrv(w.positionMsaaBuffers[i].Get(), DXGI_FORMAT_R16G16B16A16_FLOAT, w.positionMsaaSrvIndex[i], w.positionMsaaSrvCpu[i], w.positionMsaaSrvGpu[i]); 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]); for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureMsaaSrv(w.specularMsaaBuffers[i].Get(), QD3D12_SpecularAlbedoFormat, w.specularMsaaSrvIndex[i], w.specularMsaaSrvCpu[i], w.specularMsaaSrvGpu[i]); } } void QD3D12_CreateDSVForWindow(QD3D12Window& w) { D3D12_DESCRIPTOR_HEAP_DESC hd{}; hd.NumDescriptors = 3; // resolved scene depth, native/output depth, optional MSAA scene depth hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; QD3D12_CHECK(g_gl.device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&w.dsvHeap))); const UINT dsvStride = g_gl.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); D3D12_CPU_DESCRIPTOR_HANDLE lowResDsv = w.dsvHeap->GetCPUDescriptorHandleForHeapStart(); D3D12_CPU_DESCRIPTOR_HANDLE nativeDsv = lowResDsv; nativeDsv.ptr += SIZE_T(dsvStride); D3D12_CPU_DESCRIPTOR_HANDLE msaaDsv = lowResDsv; msaaDsv.ptr += SIZE_T(dsvStride) * 2; auto CreateDepthTexture = [&](UINT texWidth, UINT texHeight, UINT sampleCount, ComPtr& outRes) { D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Width = texWidth; rd.Height = texHeight; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.Format = QD3D12_DepthResourceFormat; rd.SampleDesc.Count = std::max(1u, sampleCount); rd.SampleDesc.Quality = 0; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; D3D12_CLEAR_VALUE clear{}; clear.Format = QD3D12_DepthDsvFormat; clear.DepthStencil.Depth = 1.0f; clear.DepthStencil.Stencil = 0; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear, IID_PPV_ARGS(&outRes))); }; CreateDepthTexture(w.renderWidth, w.renderHeight, 1, w.depthBuffer); CreateDepthTexture(w.width, w.height, 1, w.nativeDepthBuffer); D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc{}; dsvDesc.Format = QD3D12_DepthDsvFormat; dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; g_gl.device->CreateDepthStencilView(w.depthBuffer.Get(), &dsvDesc, lowResDsv); g_gl.device->CreateDepthStencilView(w.nativeDepthBuffer.Get(), &dsvDesc, nativeDsv); w.depthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; w.nativeDepthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; if (QD3D12_GBufferMsaaEnabled()) { CreateDepthTexture(w.renderWidth, w.renderHeight, QD3D12_GBufferSampleCount(), w.depthMsaaBuffer); D3D12_DEPTH_STENCIL_VIEW_DESC msaaDsvDesc{}; msaaDsvDesc.Format = QD3D12_DepthDsvFormat; msaaDsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; g_gl.device->CreateDepthStencilView(w.depthMsaaBuffer.Get(), &msaaDsvDesc, msaaDsv); w.depthMsaaState = D3D12_RESOURCE_STATE_DEPTH_WRITE; } else { w.depthMsaaBuffer.Reset(); w.depthMsaaState = D3D12_RESOURCE_STATE_COMMON; } if (w.depthSrvIndex == UINT_MAX) { w.depthSrvIndex = g_gl.nextSrvIndex++; w.depthSrvCpu = QD3D12_SrvCpu(w.depthSrvIndex); w.depthSrvGpu = QD3D12_SrvGpu(w.depthSrvIndex); } D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{}; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srvDesc.Format = QD3D12_DepthSrvFormat; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.Texture2D.MipLevels = 1; g_gl.device->CreateShaderResourceView(w.depthBuffer.Get(), &srvDesc, w.depthSrvCpu); if (QD3D12_GBufferMsaaEnabled() && w.depthMsaaBuffer) { if (w.depthMsaaSrvIndex == UINT_MAX) { w.depthMsaaSrvIndex = g_gl.nextSrvIndex++; w.depthMsaaSrvCpu = QD3D12_SrvCpu(w.depthMsaaSrvIndex); w.depthMsaaSrvGpu = QD3D12_SrvGpu(w.depthMsaaSrvIndex); } D3D12_SHADER_RESOURCE_VIEW_DESC msaaSrvDesc{}; msaaSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; msaaSrvDesc.Format = QD3D12_DepthSrvFormat; msaaSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; g_gl.device->CreateShaderResourceView(w.depthMsaaBuffer.Get(), &msaaSrvDesc, w.depthMsaaSrvCpu); } } static void QD3D12_CreateSrvHeap() { D3D12_DESCRIPTOR_HEAP_DESC hd{}; hd.NumDescriptors = QD3D12_MaxTextures; hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; hd.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; QD3D12_CHECK(g_gl.device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&g_gl.srvHeap))); g_gl.srvStride = g_gl.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } static D3D12_CPU_DESCRIPTOR_HANDLE QD3D12_SrvCpu(UINT index) { D3D12_CPU_DESCRIPTOR_HANDLE h = g_gl.srvHeap->GetCPUDescriptorHandleForHeapStart(); h.ptr += SIZE_T(index) * SIZE_T(g_gl.srvStride); return h; } static D3D12_GPU_DESCRIPTOR_HANDLE QD3D12_SrvGpu(UINT index) { D3D12_GPU_DESCRIPTOR_HANDLE h = g_gl.srvHeap->GetGPUDescriptorHandleForHeapStart(); h.ptr += UINT64(index) * UINT64(g_gl.srvStride); return h; } static void QD3D12_CreateCommandObjects() { QD3D12_CHECK(g_gl.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_currentWindow->frames[0].cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&g_gl.cmdList))); QD3D12_CHECK(g_gl.cmdList->Close()); QD3D12_CHECK(g_gl.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_gl.fence))); g_gl.fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); } static void QD3D12_CreateUploadRingForWindow(QD3D12Window& w) { D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_UPLOAD; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = QD3D12_UploadBufferSize; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; w.upload.size = QD3D12_UploadBufferSize; w.upload.offset = 0; for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (w.upload.resource[i]) continue; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&w.upload.resource[i]))); w.upload.gpuBase[i] = w.upload.resource[i]->GetGPUVirtualAddress(); QD3D12_CHECK(w.upload.resource[i]->Map( 0, nullptr, reinterpret_cast(&w.upload.cpuBase[i]))); } } static void QD3D12_DestroyUploadRingForWindow(QD3D12Window& w) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) { if (w.upload.resource[i] && w.upload.cpuBase[i]) w.upload.resource[i]->Unmap(0, nullptr); w.upload.cpuBase[i] = nullptr; w.upload.gpuBase[i] = 0; w.upload.resource[i].Reset(); } w.upload.size = 0; w.upload.offset = 0; } static ComPtr CompileShaderSourceVariant(const char* source, const char* entry, const char* target) { ComPtr utils; ComPtr compiler; ComPtr includeHandler; HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils)); if (FAILED(hr)) { QD3D12_Fatal("DxcCreateInstance(CLSID_DxcUtils) failed 0x%08X", (unsigned)hr); return nullptr; } hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler)); if (FAILED(hr)) { QD3D12_Fatal("DxcCreateInstance(CLSID_DxcCompiler) failed 0x%08X", (unsigned)hr); return nullptr; } hr = utils->CreateDefaultIncludeHandler(&includeHandler); if (FAILED(hr)) { QD3D12_Fatal("CreateDefaultIncludeHandler failed 0x%08X", (unsigned)hr); return nullptr; } auto Utf8ToWide = [](const char* s) -> std::wstring { if (!s || !*s) return std::wstring(); int len = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0); if (len <= 0) return std::wstring(); std::wstring out; out.resize((size_t)len - 1); MultiByteToWideChar(CP_UTF8, 0, s, -1, &out[0], len); return out; }; std::wstring wEntry = Utf8ToWide(entry); std::wstring wTarget = Utf8ToWide(target); std::wstring wName = L"InMemoryShader.hlsl"; DxcBuffer sourceBuf = {}; sourceBuf.Ptr = source; sourceBuf.Size = strlen(source); sourceBuf.Encoding = DXC_CP_UTF8; std::vector args; args.push_back(wName.c_str()); args.push_back(L"-E"); args.push_back(wEntry.c_str()); args.push_back(L"-T"); args.push_back(wTarget.c_str()); args.push_back(L"-HV"); args.push_back(L"2021"); #if defined(_DEBUG) args.push_back(L"-Zi"); args.push_back(L"-Qembed_debug"); args.push_back(L"-Od"); #else args.push_back(L"-O3"); #endif // Optional but usually good for D3D12 shaders. args.push_back(L"-all_resources_bound"); ComPtr result; hr = compiler->Compile( &sourceBuf, args.data(), (UINT32)args.size(), includeHandler.Get(), IID_PPV_ARGS(&result)); if (FAILED(hr)) { QD3D12_Fatal("DXC compile call failed for %s (target %s), hr=0x%08X", entry ? entry : "unknown", target ? target : "unknown", (unsigned)hr); return nullptr; } ComPtr errors; result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr); if (errors && errors->GetStringLength() > 0) { OutputDebugStringA(errors->GetStringPointer()); OutputDebugStringA("\n"); } HRESULT status = S_OK; result->GetStatus(&status); if (FAILED(status)) { QD3D12_Fatal("Shader compile failed for %s: %s", entry, (errors && errors->GetStringLength() > 0) ? errors->GetStringPointer() : "unknown"); return nullptr; } ComPtr dxil; result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr); if (!dxil) { QD3D12_Fatal("DXC returned no object for %s", entry); return nullptr; } ComPtr blob; blob.Attach(reinterpret_cast(dxil.Detach())); return blob; } static ComPtr CompileShaderVariant(const char* entry, const char* target) { return CompileShaderSourceVariant(kQuakeWrapperHLSL, entry, target); } static void QD3D12_CreatePostRootSignature() { D3D12_DESCRIPTOR_RANGE ranges[QD3D12_PostSrvCount]{}; for (UINT i = 0; i < _countof(ranges); ++i) { ranges[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; ranges[i].NumDescriptors = 1; ranges[i].BaseShaderRegister = i; ranges[i].RegisterSpace = 0; ranges[i].OffsetInDescriptorsFromTableStart = 0; } D3D12_ROOT_PARAMETER params[QD3D12_PostSrvCount + 1]{}; for (UINT i = 0; i < QD3D12_PostSrvCount; ++i) { params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[i].DescriptorTable.NumDescriptorRanges = 1; params[i].DescriptorTable.pDescriptorRanges = &ranges[i]; params[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; } params[QD3D12_PostRootConstants].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; params[QD3D12_PostRootConstants].Constants.Num32BitValues = QD3D12_PostConstantDwords; params[QD3D12_PostRootConstants].Constants.ShaderRegister = 0; params[QD3D12_PostRootConstants].Constants.RegisterSpace = 0; params[QD3D12_PostRootConstants].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; D3D12_STATIC_SAMPLER_DESC samp{}; samp.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; samp.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samp.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samp.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samp.ShaderRegister = 0; samp.RegisterSpace = 0; samp.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; samp.MaxLOD = D3D12_FLOAT32_MAX; D3D12_ROOT_SIGNATURE_DESC rsd{}; rsd.NumParameters = _countof(params); rsd.pParameters = params; rsd.NumStaticSamplers = 1; rsd.pStaticSamplers = &samp; rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; ComPtr sig; ComPtr err; QD3D12_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err)); QD3D12_CHECK(g_gl.device->CreateRootSignature(0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(&g_gl.postRootSig))); } static void QD3D12_CreateRootSignature() { D3D12_DESCRIPTOR_RANGE ranges[QD3D12_MaxTextureUnits] = {}; D3D12_ROOT_PARAMETER params[1 + QD3D12_MaxTextureUnits] = {}; params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; params[0].Descriptor.ShaderRegister = 0; params[0].Descriptor.RegisterSpace = 0; params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { ranges[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; ranges[i].NumDescriptors = 1; ranges[i].BaseShaderRegister = i; ranges[i].RegisterSpace = 0; ranges[i].OffsetInDescriptorsFromTableStart = 0; params[1 + i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[1 + i].DescriptorTable.NumDescriptorRanges = 1; params[1 + i].DescriptorTable.pDescriptorRanges = &ranges[i]; params[1 + i].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; } D3D12_STATIC_SAMPLER_DESC samps[QD3D12_MaxTextureUnits] = {}; for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i) { samps[i].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; samps[i].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samps[i].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samps[i].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samps[i].ShaderRegister = i; samps[i].RegisterSpace = 0; samps[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; samps[i].MaxLOD = D3D12_FLOAT32_MAX; } D3D12_ROOT_SIGNATURE_DESC rsd{}; rsd.NumParameters = _countof(params); rsd.pParameters = params; rsd.NumStaticSamplers = _countof(samps); rsd.pStaticSamplers = samps; rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; ComPtr sig; ComPtr err; QD3D12_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err)); QD3D12_CHECK(g_gl.device->CreateRootSignature(0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(&g_gl.rootSig))); QD3D12_CreatePostRootSignature(); } static void QD3D12_CompileShaders() { g_gl.vsMainBlob = CompileShaderVariant("VSMain", "vs_6_0"); g_gl.hsMainBlob = CompileShaderVariant("HSMain", "hs_6_0"); g_gl.dsMainBlob = CompileShaderVariant("DSMain", "ds_6_0"); g_gl.psMainBlob = CompileShaderVariant("PSMain", "ps_6_0"); g_gl.psAlphaBlob = CompileShaderVariant("PSMainAlphaTest", "ps_6_0"); g_gl.psUntexturedBlob = CompileShaderVariant("PSMainUntextured", "ps_6_0"); g_gl.psMainColorOnlyBlob = CompileShaderVariant("PSMainColorOnly", "ps_6_0"); g_gl.psAlphaColorOnlyBlob = CompileShaderVariant("PSMainAlphaTestColorOnly", "ps_6_0"); g_gl.psUntexturedColorOnlyBlob = CompileShaderVariant("PSMainUntexturedColorOnly", "ps_6_0"); g_gl.postVsBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "VSMain", "vs_6_0"); g_gl.postPsCopyBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSCopy", "ps_6_0"); g_gl.postPsSharpenBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSSharpen", "ps_6_0"); g_gl.postPsToneMapBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMap", "ps_6_0"); g_gl.postPsToneMapTileMaxBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMapTileMax", "ps_6_0"); g_gl.postPsToneMapFinalMaxBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMapFinalMax", "ps_6_0"); g_gl.postPsTaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSTAA", "ps_6_0"); g_gl.postPsAddBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSAdd", "ps_6_0"); g_gl.postPsDepthCopyBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthCopy", "ps_6_0"); g_gl.postPsDepthResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthResolveMS", "ps_6_0"); g_gl.postPsGBufferPointResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSGBufferPointResolveMS", "ps_6_0"); } static const D3D12_INPUT_ELEMENT_DESC kGLVertexInputLayout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, px), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, nx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(GLVertex, u0), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(GLVertex, u1), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(GLVertex, r), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, tx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "BINORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, bx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, }; static const D3D12_INPUT_ELEMENT_DESC kGLVertexInputLayoutARB[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, px), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, nx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(GLVertex, u0), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(GLVertex, u1), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(GLVertex, r), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, tx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "BINORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, bx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, }; static UINT8 BuildColorWriteMask() { UINT8 mask = 0; if (g_gl.colorMaskR) mask |= D3D12_COLOR_WRITE_ENABLE_RED; if (g_gl.colorMaskG) mask |= D3D12_COLOR_WRITE_ENABLE_GREEN; if (g_gl.colorMaskB) mask |= D3D12_COLOR_WRITE_ENABLE_BLUE; if (g_gl.colorMaskA) mask |= D3D12_COLOR_WRITE_ENABLE_ALPHA; return mask; } static D3D12_CPU_DESCRIPTOR_HANDLE CurrentNormalRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_NORMAL_RENDER, w.frameIndex); } static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( PipelineMode mode, ID3DBlob* vs, ID3DBlob* hs, ID3DBlob* ds, ID3DBlob* ps, const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { D3D12_GRAPHICS_PIPELINE_STATE_DESC d{}; d.pRootSignature = g_gl.rootSig.Get(); d.VS = { vs->GetBufferPointer(), vs->GetBufferSize() }; const bool useTessellation = QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) && hs && ds; if (useTessellation) { d.HS = { hs->GetBufferPointer(), hs->GetBufferSize() }; d.DS = { ds->GetBufferPointer(), ds->GetBufferSize() }; } d.PS = { ps->GetBufferPointer(), ps->GetBufferSize() }; d.InputLayout.pInputElementDescs = kGLVertexInputLayout; d.InputLayout.NumElements = _countof(kGLVertexInputLayout); d.PrimitiveTopologyType = useTessellation ? D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH : topoType; d.NumRenderTargets = nativeColorOnly ? 1u : 6u; 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.RTVFormats[5] = QD3D12_SpecularAlbedoFormat; } d.DSVFormat = QD3D12_DepthDsvFormat; d.SampleDesc.Count = nativeColorOnly ? 1u : QD3D12_GBufferSampleCount(); d.SampleDesc.Quality = 0; d.SampleMask = UINT_MAX; d.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; d.RasterizerState.DepthClipEnable = TRUE; d.RasterizerState.MultisampleEnable = (d.SampleDesc.Count > 1) ? TRUE : FALSE; const bool alphaBlended = QD3D12_PipelineUsesAlphaBlend(mode); d.BlendState.RenderTarget[0].RenderTargetWriteMask = key.colorWriteMask; if (!nativeColorOnly) { d.BlendState.RenderTarget[1].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[2].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[3].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[4].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; d.BlendState.RenderTarget[5].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; } d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = nativeColorOnly ? FALSE : TRUE; for (int i = 0; i < (nativeColorOnly ? 1 : 6); ++i) { auto& rt = d.BlendState.RenderTarget[i]; rt.BlendEnable = FALSE; rt.LogicOpEnable = FALSE; rt.SrcBlend = D3D12_BLEND_ONE; rt.DestBlend = D3D12_BLEND_ZERO; rt.BlendOp = D3D12_BLEND_OP_ADD; rt.SrcBlendAlpha = D3D12_BLEND_ONE; rt.DestBlendAlpha = D3D12_BLEND_ZERO; rt.BlendOpAlpha = D3D12_BLEND_OP_ADD; rt.LogicOp = D3D12_LOGIC_OP_NOOP; } if (alphaBlended) { auto& rt = d.BlendState.RenderTarget[0]; rt.BlendEnable = TRUE; rt.SrcBlend = MapBlend(key.blendSrc); rt.DestBlend = MapBlend(key.blendDst); rt.BlendOp = D3D12_BLEND_OP_ADD; rt.SrcBlendAlpha = MapBlendAlpha(key.blendSrc); rt.DestBlendAlpha = MapBlendAlpha(key.blendDst); rt.BlendOpAlpha = D3D12_BLEND_OP_ADD; //if (!nativeColorOnly) //{ // // Transparent passes happen after glLightScene in the RTCW path, so they // // must not scribble over the opaque G-buffer or motion vectors that the // // raytracing and upscaling passes depend on. // d.BlendState.RenderTarget[1].RenderTargetWriteMask = 0; // d.BlendState.RenderTarget[2].RenderTargetWriteMask = 0; // d.BlendState.RenderTarget[3].RenderTargetWriteMask = 0; //} } BatchKey depthStencilKey = key; if (alphaBlended) depthStencilKey.depthWrite = false; ApplyRasterDepthStencilState(d, depthStencilKey); if (useTessellation) { // Hull/domain shader triangle orientation can differ on legacy content that // flips GL front-face state. Avoid dropping tessellated patches entirely; // depth still rejects hidden surfaces. d.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; } return d; } static void QD3D12_CreatePSOs() { D3D12_GRAPHICS_PIPELINE_STATE_DESC d{}; d.pRootSignature = g_gl.postRootSig.Get(); d.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() }; d.PS = { g_gl.postPsCopyBlob->GetBufferPointer(), g_gl.postPsCopyBlob->GetBufferSize() }; d.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; d.SampleDesc.Count = 1; d.SampleMask = UINT_MAX; d.NumRenderTargets = 1; d.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; d.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; d.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; d.RasterizerState.DepthClipEnable = TRUE; d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = FALSE; d.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; d.BlendState.RenderTarget[0].BlendEnable = FALSE; d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE; d.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ZERO; d.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; d.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO; d.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; d.DepthStencilState.DepthEnable = FALSE; d.DepthStencilState.StencilEnable = FALSE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postCopyPSO))); d.PS = { g_gl.postPsSharpenBlob->GetBufferPointer(), g_gl.postPsSharpenBlob->GetBufferSize() }; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postSharpenPSO))); d.PS = { g_gl.postPsToneMapBlob->GetBufferPointer(), g_gl.postPsToneMapBlob->GetBufferSize() }; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postToneMapPSO))); D3D12_GRAPHICS_PIPELINE_STATE_DESC reduceDesc = d; reduceDesc.RTVFormats[0] = QD3D12_StreamlineOutputFormat; reduceDesc.PS = { g_gl.postPsToneMapTileMaxBlob->GetBufferPointer(), g_gl.postPsToneMapTileMaxBlob->GetBufferSize() }; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&reduceDesc, IID_PPV_ARGS(&g_gl.postToneMapTileMaxPSO))); reduceDesc.PS = { g_gl.postPsToneMapFinalMaxBlob->GetBufferPointer(), g_gl.postPsToneMapFinalMaxBlob->GetBufferSize() }; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&reduceDesc, IID_PPV_ARGS(&g_gl.postToneMapFinalMaxPSO))); D3D12_GRAPHICS_PIPELINE_STATE_DESC taaDesc = d; taaDesc.PS = { g_gl.postPsTaaBlob->GetBufferPointer(), g_gl.postPsTaaBlob->GetBufferSize() }; taaDesc.RTVFormats[0] = QD3D12_StreamlineOutputFormat; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&taaDesc, IID_PPV_ARGS(&g_gl.postTaaPSO))); d.PS = { g_gl.postPsAddBlob->GetBufferPointer(), g_gl.postPsAddBlob->GetBufferSize() }; d.BlendState.RenderTarget[0].BlendEnable = TRUE; d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE; d.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ONE; d.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postAdditivePSO))); D3D12_GRAPHICS_PIPELINE_STATE_DESC depthDesc{}; depthDesc.pRootSignature = g_gl.postRootSig.Get(); depthDesc.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() }; depthDesc.PS = { g_gl.postPsDepthCopyBlob->GetBufferPointer(), g_gl.postPsDepthCopyBlob->GetBufferSize() }; depthDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; depthDesc.SampleDesc.Count = 1; depthDesc.SampleMask = UINT_MAX; depthDesc.NumRenderTargets = 0; depthDesc.DSVFormat = QD3D12_DepthDsvFormat; depthDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; depthDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; depthDesc.RasterizerState.DepthClipEnable = TRUE; depthDesc.BlendState.AlphaToCoverageEnable = FALSE; depthDesc.BlendState.IndependentBlendEnable = FALSE; depthDesc.DepthStencilState.DepthEnable = TRUE; depthDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; depthDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; depthDesc.DepthStencilState.StencilEnable = FALSE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&depthDesc, IID_PPV_ARGS(&g_gl.postDepthCopyPSO))); depthDesc.PS = { g_gl.postPsDepthResolveMsaaBlob->GetBufferPointer(), g_gl.postPsDepthResolveMsaaBlob->GetBufferSize() }; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&depthDesc, IID_PPV_ARGS(&g_gl.postDepthResolveMsaaPSO))); D3D12_GRAPHICS_PIPELINE_STATE_DESC gbufferResolveDesc{}; gbufferResolveDesc.pRootSignature = g_gl.postRootSig.Get(); gbufferResolveDesc.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() }; gbufferResolveDesc.PS = { g_gl.postPsGBufferPointResolveMsaaBlob->GetBufferPointer(), g_gl.postPsGBufferPointResolveMsaaBlob->GetBufferSize() }; gbufferResolveDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; gbufferResolveDesc.SampleDesc.Count = 1; gbufferResolveDesc.SampleMask = UINT_MAX; gbufferResolveDesc.NumRenderTargets = 5; 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.RTVFormats[4] = QD3D12_SpecularAlbedoFormat; 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 < 5; ++rt) { gbufferResolveDesc.BlendState.RenderTarget[rt].BlendEnable = FALSE; gbufferResolveDesc.BlendState.RenderTarget[rt].LogicOpEnable = FALSE; gbufferResolveDesc.BlendState.RenderTarget[rt].SrcBlend = D3D12_BLEND_ONE; gbufferResolveDesc.BlendState.RenderTarget[rt].DestBlend = D3D12_BLEND_ZERO; gbufferResolveDesc.BlendState.RenderTarget[rt].BlendOp = D3D12_BLEND_OP_ADD; gbufferResolveDesc.BlendState.RenderTarget[rt].SrcBlendAlpha = D3D12_BLEND_ONE; gbufferResolveDesc.BlendState.RenderTarget[rt].DestBlendAlpha = D3D12_BLEND_ZERO; gbufferResolveDesc.BlendState.RenderTarget[rt].BlendOpAlpha = D3D12_BLEND_OP_ADD; gbufferResolveDesc.BlendState.RenderTarget[rt].LogicOp = D3D12_LOGIC_OP_NOOP; gbufferResolveDesc.BlendState.RenderTarget[rt].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; } gbufferResolveDesc.DepthStencilState.DepthEnable = FALSE; gbufferResolveDesc.DepthStencilState.StencilEnable = FALSE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&gbufferResolveDesc, IID_PPV_ARGS(&g_gl.postGBufferPointResolveMsaaPSO))); } static uint64_t MakePSOKey( const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { uint64_t h = 1469598103934665603ull; auto mix = [&](uint64_t v) { h ^= v; h *= 1099511628211ull; }; auto mixFloat = [&](float f) { uint32_t bits = 0; memcpy(&bits, &f, sizeof(bits)); mix(bits); }; mix(uint32_t(key.pipeline)); mix(uint32_t(key.blendSrc)); mix(uint32_t(key.blendDst)); mix(key.depthTest ? 1ull : 0ull); mix(key.depthWrite ? 1ull : 0ull); mix(uint32_t(key.depthFunc)); mix(key.polygonOffsetEnabled ? 1ull : 0ull); mixFloat(key.polygonOffsetEnabled ? key.polygonOffsetFactor : 0.0f); mixFloat(key.polygonOffsetEnabled ? key.polygonOffsetUnits : 0.0f); mix(uint32_t(topoType)); mix(QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) ? 1ull : 0ull); mix(nativeColorOnly ? 1ull : 0ull); mix(nativeColorOnly ? 1ull : uint64_t(QD3D12_GBufferSampleCount())); mix(uint32_t(key.colorWriteMask)); mix(key.cullFaceEnabled ? 1ull : 0ull); mix(uint32_t(key.cullMode)); mix(uint32_t(key.frontFace)); mix(key.stencilTest ? 1ull : 0ull); mix(uint32_t(key.stencilReadMask)); mix(uint32_t(key.stencilWriteMask)); mix(uint32_t(key.stencilFrontFunc)); mix(uint32_t(key.stencilFrontSFail)); mix(uint32_t(key.stencilFrontDPFail)); mix(uint32_t(key.stencilFrontDPPass)); mix(uint32_t(key.stencilBackFunc)); mix(uint32_t(key.stencilBackSFail)); mix(uint32_t(key.stencilBackDPFail)); mix(uint32_t(key.stencilBackDPPass)); return h; } static std::unordered_map> g_psoCache; static ID3D12PipelineState* QD3D12_GetPSO( const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { const uint64_t psoKey = MakePSOKey(key, topoType, nativeColorOnly); auto it = g_psoCache.find(psoKey); if (it != g_psoCache.end()) return it->second.Get(); ID3DBlob* psBlob = nullptr; switch (key.pipeline) { case PIPE_ALPHA_TEST_TEX: psBlob = nativeColorOnly ? g_gl.psAlphaColorOnlyBlob.Get() : g_gl.psAlphaBlob.Get(); break; case PIPE_OPAQUE_UNTEX: case PIPE_BLEND_UNTEX: psBlob = nativeColorOnly ? g_gl.psUntexturedColorOnlyBlob.Get() : g_gl.psUntexturedBlob.Get(); break; case PIPE_OPAQUE_TEX: case PIPE_BLEND_TEX: default: psBlob = nativeColorOnly ? g_gl.psMainColorOnlyBlob.Get() : g_gl.psMainBlob.Get(); break; } auto desc = BuildPSODesc( key.pipeline, g_gl.vsMainBlob.Get(), g_gl.hsMainBlob.Get(), g_gl.dsMainBlob.Get(), psBlob, key, topoType, nativeColorOnly); ComPtr newPSO; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&newPSO))); ID3D12PipelineState* out = newPSO.Get(); g_psoCache.emplace(psoKey, std::move(newPSO)); return out; } static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( ID3DBlob* vs, ID3DBlob* ps, const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { D3D12_GRAPHICS_PIPELINE_STATE_DESC d{}; d.pRootSignature = g_gl.rootSig.Get(); d.VS = { vs->GetBufferPointer(), vs->GetBufferSize() }; d.PS = { ps->GetBufferPointer(), ps->GetBufferSize() }; d.InputLayout.pInputElementDescs = kGLVertexInputLayoutARB; d.InputLayout.NumElements = _countof(kGLVertexInputLayoutARB); d.PrimitiveTopologyType = topoType; d.NumRenderTargets = nativeColorOnly ? 1u : 6u; 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.RTVFormats[5] = QD3D12_SpecularAlbedoFormat; } d.DSVFormat = QD3D12_DepthDsvFormat; d.SampleDesc.Count = nativeColorOnly ? 1u : QD3D12_GBufferSampleCount(); d.SampleDesc.Quality = 0; d.SampleMask = UINT_MAX; d.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; d.RasterizerState.DepthClipEnable = TRUE; d.RasterizerState.MultisampleEnable = (d.SampleDesc.Count > 1) ? TRUE : FALSE; const bool alphaBlended = QD3D12_PipelineUsesAlphaBlend(key.pipeline); d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = nativeColorOnly ? FALSE : TRUE; for (int i = 0; i < (nativeColorOnly ? 1 : 6); ++i) { auto& rt = d.BlendState.RenderTarget[i]; rt.BlendEnable = FALSE; rt.LogicOpEnable = FALSE; rt.SrcBlend = D3D12_BLEND_ONE; rt.DestBlend = D3D12_BLEND_ZERO; rt.BlendOp = D3D12_BLEND_OP_ADD; rt.SrcBlendAlpha = D3D12_BLEND_ONE; rt.DestBlendAlpha = D3D12_BLEND_ZERO; rt.BlendOpAlpha = D3D12_BLEND_OP_ADD; rt.LogicOp = D3D12_LOGIC_OP_NOOP; rt.RenderTargetWriteMask = (i == 0) ? key.colorWriteMask : 0; } if (alphaBlended) { auto& rt = d.BlendState.RenderTarget[0]; rt.BlendEnable = TRUE; rt.SrcBlend = MapBlend(key.blendSrc); rt.DestBlend = MapBlend(key.blendDst); rt.BlendOp = D3D12_BLEND_OP_ADD; rt.SrcBlendAlpha = MapBlendAlpha(key.blendSrc); rt.DestBlendAlpha = MapBlendAlpha(key.blendDst); rt.BlendOpAlpha = D3D12_BLEND_OP_ADD; } BatchKey depthStencilKey = key; if (alphaBlended) depthStencilKey.depthWrite = false; ApplyRasterDepthStencilState(d, depthStencilKey); return d; } static uint64_t MakeARBPSOKey( uint64_t programKey, const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { uint64_t h = programKey ? programKey : 1469598103934665603ull; auto mix = [&](uint64_t v) { h ^= v + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); }; auto mixFloat = [&](float f) { uint32_t bits = 0; memcpy(&bits, &f, sizeof(bits)); mix(bits); }; mix(uint32_t(key.pipeline)); mix(uint32_t(key.blendSrc)); mix(uint32_t(key.blendDst)); mix(key.depthTest ? 1ull : 0ull); mix(key.depthWrite ? 1ull : 0ull); mix(uint32_t(key.depthFunc)); mix(key.polygonOffsetEnabled ? 1ull : 0ull); mixFloat(key.polygonOffsetEnabled ? key.polygonOffsetFactor : 0.0f); mixFloat(key.polygonOffsetEnabled ? key.polygonOffsetUnits : 0.0f); mix(uint32_t(topoType)); mix(nativeColorOnly ? 1ull : 0ull); mix(nativeColorOnly ? 1ull : uint64_t(QD3D12_GBufferSampleCount())); mix(uint32_t(key.colorWriteMask)); mix(key.cullFaceEnabled ? 1ull : 0ull); mix(uint32_t(key.cullMode)); mix(uint32_t(key.frontFace)); mix(key.stencilTest ? 1ull : 0ull); mix(uint32_t(key.stencilReadMask)); mix(uint32_t(key.stencilWriteMask)); mix(uint32_t(key.stencilFrontFunc)); mix(uint32_t(key.stencilFrontSFail)); mix(uint32_t(key.stencilFrontDPFail)); mix(uint32_t(key.stencilFrontDPPass)); mix(uint32_t(key.stencilBackFunc)); mix(uint32_t(key.stencilBackSFail)); mix(uint32_t(key.stencilBackDPFail)); mix(uint32_t(key.stencilBackDPPass)); return h; } static std::unordered_map> g_arbPsoCache; static ID3D12PipelineState* QD3D12_GetARBPSO( const BatchKey& key, D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType, bool nativeColorOnly) { if (!key.arbVertexBlob || !key.arbFragmentBlob) return nullptr; const uint64_t programKey = (uint64_t(key.arbVertexProgram) << 48ull) ^ (uint64_t(key.arbFragmentProgram) << 32ull) ^ (uint64_t(key.arbVertexRevision) << 16ull) ^ uint64_t(key.arbFragmentRevision); const uint64_t psoKey = MakeARBPSOKey( programKey, key, topoType, nativeColorOnly); auto it = g_arbPsoCache.find(psoKey); if (it != g_arbPsoCache.end()) return it->second.Get(); auto desc = BuildARBPSODesc( key.arbVertexBlob, key.arbFragmentBlob, key, topoType, nativeColorOnly); ComPtr newPSO; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&newPSO))); ID3D12PipelineState* out = newPSO.Get(); g_arbPsoCache.emplace(psoKey, std::move(newPSO)); return out; } static void QD3D12_CreateWhiteTexture() { g_gl.whiteTexture.glId = 0; g_gl.whiteTexture.width = 1; g_gl.whiteTexture.height = 1; g_gl.whiteTexture.format = GL_RGBA; g_gl.whiteTexture.sysmem = { 255, 255, 255, 255 }; g_gl.whiteTexture.srvIndex = 0; g_gl.whiteTexture.srvCpu = QD3D12_SrvCpu(0); g_gl.whiteTexture.srvGpu = QD3D12_SrvGpu(0); D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Width = 1; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; D3D12_HEAP_PROPERTIES hpDef{}; hpDef.Type = D3D12_HEAP_TYPE_DEFAULT; QD3D12_CHECK(g_gl.device->CreateCommittedResource(&hpDef, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&g_gl.whiteTexture.texture))); g_gl.whiteTexture.state = D3D12_RESOURCE_STATE_COPY_DEST; D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; sd.Texture2D.MipLevels = g_gl.whiteTexture.mipLevels; g_gl.device->CreateShaderResourceView(g_gl.whiteTexture.texture.Get(), &sd, g_gl.whiteTexture.srvCpu); // Upload 1x1 white using a one-time command list. QD3D12_CHECK(g_currentWindow->frames[g_currentWindow->frameIndex].cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(g_currentWindow->frames[g_currentWindow->frameIndex].cmdAlloc.Get(), nullptr)); const UINT64 uploadPitch = 256; const UINT64 uploadSize = uploadPitch; UploadAlloc alloc = QD3D12_AllocUpload((UINT)uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); memset(alloc.cpu, 0, uploadSize); ((uint8_t*)alloc.cpu)[0] = 255; ((uint8_t*)alloc.cpu)[1] = 255; ((uint8_t*)alloc.cpu)[2] = 255; ((uint8_t*)alloc.cpu)[3] = 255; D3D12_TEXTURE_COPY_LOCATION dst{}; dst.pResource = g_gl.whiteTexture.texture.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dst.SubresourceIndex = 0; D3D12_TEXTURE_COPY_LOCATION src{}; src.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(); src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint.Offset = alloc.offset; src.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; src.PlacedFootprint.Footprint.Width = 1; src.PlacedFootprint.Footprint.Height = 1; src.PlacedFootprint.Footprint.Depth = 1; src.PlacedFootprint.Footprint.RowPitch = (UINT)uploadPitch; g_gl.cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = g_gl.whiteTexture.texture.Get(); b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &b); g_gl.whiteTexture.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); QD3D12_WaitForGPU(); g_gl.whiteTexture.gpuValid = true; QD3D12_ResetUploadRing(); } static void QD3D12_UpdateViewportState() { QD3D12Window& w = *g_currentWindow; const UINT activeWidth = QD3D12_ActiveRasterWidth(w); const UINT activeHeight = QD3D12_ActiveRasterHeight(w); const float scaleX = (w.width > 0) ? ((float)activeWidth / (float)w.width) : 1.0f; const float scaleY = (w.height > 0) ? ((float)activeHeight / (float)w.height) : 1.0f; const GLint rx = (GLint)std::lround((double)g_gl.viewportX * scaleX); const GLint ry = (GLint)std::lround((double)g_gl.viewportY * scaleY); const GLint rw = (GLint)std::lround((double)g_gl.viewportW * scaleX); const GLint rh = (GLint)std::lround((double)g_gl.viewportH * scaleY); const GLint rasterHeight = (GLint)activeHeight; w.viewport.TopLeftX = (float)rx; w.viewport.TopLeftY = (float)(rasterHeight - (ry + rh)); w.viewport.Width = (float)rw; w.viewport.Height = (float)rh; w.viewport.MinDepth = (float)ClampValue(g_gl.depthRangeNear, 0.0, 1.0); w.viewport.MaxDepth = (float)ClampValue(g_gl.depthRangeFar, 0.0, 1.0); w.scissor.left = rx; w.scissor.top = rasterHeight - (ry + rh); w.scissor.right = rx + rw; w.scissor.bottom = rasterHeight - ry; } static void QD3D12_CreateSurfaceFrameResources(QD3D12Window& surf) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) { surf.frames[i].fenceValue = 0; QD3D12_CHECK( g_gl.device->CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&surf.frames[i].cmdAlloc))); } } bool QD3D12_InitForQuakeWindow(struct QD3D12Window* window, HWND hwnd, int width, int height, bool fastPath) { window->hwnd = hwnd; window->isPbuffer = false; if (!window->hdc && hwnd) { window->hdc = GetDC(hwnd); window->ownsHdc = (window->hdc != nullptr); } QD3D12_RegisterWindowDC(window); if (!g_qd3d12CurrentRC && window->hdc) g_qd3d12CurrentRC = QD3D12_CreateGLContextHandle(window->hdc, window); g_qd3d12CurrentDC = window->hdc; window->width = (UINT)width; window->height = (UINT)height; QD3D12_SelectRenderResolution(*window, window->width, window->height); g_gl.viewportW = width; g_gl.viewportH = height; g_currentWindow = window; if (!fastPath) { QD3D12_InitFrameVertexArena(); QD3D12_CreateDevice(); QD3D12_SelectRenderResolution(*window, window->width, window->height); } QD3D12_CreateSurfaceFrameResources(*window); QD3D12_CreateSwapChainForWindow(*window); // SRV heap must exist before any code that allocates SRV indices/handles. if (!fastPath) { QD3D12_CreateSrvHeap(); } QD3D12_CreateRTVsForWindow(*window); QD3D12_CreateDSVForWindow(*window); if (!fastPath) { QD3D12_CreateCommandObjects(); } QD3D12_CreateUploadRingForWindow(*window); if (!fastPath) { QD3D12_CompileShaders(); QD3D12_CreateRootSignature(); QD3D12_CreatePSOs(); QD3D12_UpdateViewportState(); QD3D12_CreateWhiteTexture(); QD3D12_CreateNeuralPOMZeroBuffer(); QD3D12_CreateOcclusionQueryObjects(); QD3D12ARB_Init(); } g_gl.motionHistoryReset = true; QD3D12_Log("QD3D12 initialized: output=%ux%u render=%ux%u gbufferSamples=%ux", window->width, window->height, window->renderWidth, window->renderHeight, QD3D12_GBufferSampleCount()); return true; } void QD3D12_ShutdownForQuake() { if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); #if defined(QD3D12_ENABLE_FFX) QD3D12_DestroyFfxUpscaleContext(); #endif QD3D12_ShutdownFrameVertexArena(); QD3D12_WaitForGPU(); QD3D12_ShutdownMipWorkers(); #if defined(QD3D12_ENABLE_STREAMLINE) if (g_qd3d12Sl.initialized) { slShutdown(); g_qd3d12Sl = QD3D12StreamlineState{}; } #endif if (g_gl.fenceEvent) CloseHandle(g_gl.fenceEvent); QD3D12ARB_Shutdown(); QD3D12_ResetAutoCameraHistory(); g_arbPsoCache.clear(); g_qd3d12RaytracingMeshMaterialFlags.clear(); g_gl = GLState{}; } // ============================================================ // SECTION 8: frame control // ============================================================ static D3D12_CPU_DESCRIPTOR_HANDLE CurrentRTV() { QD3D12Window& w = *g_currentWindow; return QD3D12_RtvAt(w, QD3D12_RTV_SCENE_RENDER, w.frameIndex); } void QD3D12_BeginFrame() { QD3D12Window& w = *g_currentWindow; if (g_gl.frameOpen) { if (g_gl.frameOwner == &w) return; QD3D12_SubmitOpenFrameNoPresentAndWait(); } w.frameIndex = w.swapChain ? w.swapChain->GetCurrentBackBufferIndex() : 0; QD3D12_WaitForFrame(w.frameIndex); QD3D12_ResetUploadRing(); g_gl.framePhase = QD3D12_FRAME_LOW_RES; g_gl.sceneResolvedThisFrame = false; g_gl.gbufferResolvedThisFrame = false; g_gl.raytracedLightingReadyThisFrame = false; g_gl.sceneFogValidThisFrame = false; QD3D12_UpdateViewportState(); FrameResources& fr = w.frames[w.frameIndex]; QD3D12_CHECK(fr.cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(fr.cmdAlloc.Get(), nullptr)); QD3D12_BindTargetsForCurrentPhase(w); 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 }; const float specularClear[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.cmdList->ClearRenderTargetView(CurrentSpecularRTV(), specularClear, 0, nullptr); g_gl.frameOpen = true; g_gl.frameOwner = &w; } void QD3D12_EndFrame() { QD3D12Window& w = *g_currentWindow; if (g_gl.frameOpen && g_gl.frameOwner != &w) { QD3D12_SubmitOpenFrameNoPresentAndWait(); } if (!g_gl.frameOpen) { if (g_gl.queuedBatches.empty()) return; QD3D12_BeginFrame(); } QD3D12_FlushQueuedBatches(); 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); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.positionBuffers[w.frameIndex].Get(), w.positionBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.velocityBuffers[w.frameIndex].Get(), w.velocityBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.specularBuffers[w.frameIndex].Get(), w.specularBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.depthBuffer.Get(), w.depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_RunUpscalerOrBlit(w); } // Do the nonblocking mipchain handoff after the frame's draws/blit are recorded. // This keeps first-use texture uploads to mip 0 and spreads completed mip uploads. QD3D12_ProcessCompletedTextureMipJobs(2); QD3D12_TransitionResource(g_gl.cmdList.Get(), w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PRESENT); QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); g_gl.frameOpen = false; g_gl.frameOwner = nullptr; if (!w.isPbuffer) QD3D12_CommitMotionHistoryForFrame(); g_gl.framePhase = QD3D12_FRAME_LOW_RES; g_gl.sceneResolvedThisFrame = false; g_gl.gbufferResolvedThisFrame = false; g_gl.raytracedLightingReadyThisFrame = false; g_gl.sceneFogValidThisFrame = false; QD3D12_UpdateViewportState(); } void QD3D12_Present() { QD3D12Window& w = *g_currentWindow; if (w.swapChain) QD3D12_CHECK(w.swapChain->Present(0, DXGI_PRESENT_ALLOW_TEARING)); FrameResources& fr = w.frames[w.frameIndex]; const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); fr.fenceValue = signalValue; } static void QD3D12_SubmitOpenFrameNoPresentAndWait() { if (!g_gl.device || !g_gl.queue || !g_gl.cmdList) return; QD3D12Window* owner = g_gl.frameOwner ? g_gl.frameOwner : g_currentWindow; if (!owner) return; QD3D12Window* savedWindow = g_currentWindow; g_currentWindow = owner; if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) { QD3D12_EndFrame(); FrameResources& fr = owner->frames[owner->frameIndex]; const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); fr.fenceValue = signalValue; if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } QD3D12_CollectRetiredResources(); QD3D12_ResetFrameVertexArena(); } g_currentWindow = savedWindow; } void QD3D12_SwapBuffers(HDC hdc) { if (g_gl.frameOpen && g_gl.frameOwner && g_gl.frameOwner != g_currentWindow) QD3D12_SubmitOpenFrameNoPresentAndWait(); QD3D12_EndFrame(); QD3D12_Present(); QD3D12_CollectRetiredResources(); QD3D12_ResetFrameVertexArena(); } // ============================================================ // SECTION 9: texture upload/update // ============================================================ static UINT QD3D12_CalcMipCount(int width, int height) { if (width <= 0 || height <= 0) return 1; UINT levels = 1; UINT w = (UINT)width; UINT h = (UINT)height; while (w > 1 || h > 1) { w = std::max(1u, w >> 1); h = std::max(1u, h >> 1); ++levels; if (levels > 2) break; } return levels; } static UINT QD3D12_DesiredTextureMipLevels(const TextureResource& tex) { // Compressed uploads stay at one level in this shim because we do not encode // generated BC/DXT blocks. Uncompressed texture storage gets a full CPU-built // mip chain whenever level 0 sysmem exists. if (tex.compressed || tex.width <= 0 || tex.height <= 0 || tex.sysmem.empty()) return 1; return QD3D12_CalcMipCount(tex.width, tex.height); } static bool QD3D12_TextureWantsMipSampling(const TextureResource& tex) { switch (tex.minFilter) { case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_LINEAR: return true; case GL_NEAREST: case GL_LINEAR: default: return false; } } static UINT QD3D12_TextureVisibleMipLevels(const TextureResource& tex) { if (!QD3D12_TextureWantsMipSampling(tex)) return 1; if (!tex.mipChainResident || tex.mipChainResidentGeneration != tex.cpuGeneration) return 1; return std::max(1u, tex.mipLevels); } static void EnsureTextureResource(TextureResource& tex) { const DXGI_FORMAT dxgiFormat = tex.dxgiFormat; if (tex.width <= 0 || tex.height <= 0 || dxgiFormat == DXGI_FORMAT_UNKNOWN) return; const UINT desiredMipLevels = QD3D12_DesiredTextureMipLevels(tex); auto EnsureSrvDescriptor = [&]() { if (!tex.texture) return; if (tex.srvIndex == UINT_MAX) { tex.srvIndex = g_gl.nextSrvIndex++; tex.srvCpu = QD3D12_SrvCpu(tex.srvIndex); tex.srvGpu = QD3D12_SrvGpu(tex.srvIndex); } D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; sd.Format = dxgiFormat; sd.Shader4ComponentMapping = QD3D12_TextureShaderComponentMapping(tex); sd.Texture2D.MostDetailedMip = 0; sd.Texture2D.MipLevels = QD3D12_TextureVisibleMipLevels(tex); sd.Texture2D.ResourceMinLODClamp = 0.0f; g_gl.device->CreateShaderResourceView(tex.texture.Get(), &sd, tex.srvCpu); }; bool needsRecreate = false; if (!tex.texture) { needsRecreate = true; } else { D3D12_RESOURCE_DESC desc = tex.texture->GetDesc(); if ((int)desc.Width != tex.width || (int)desc.Height != tex.height || desc.Format != dxgiFormat || desc.MipLevels != desiredMipLevels) { QD3D12_RetireResource(tex.texture); tex.gpuValid = false; tex.mipChainResident = false; tex.mipChainResidentGeneration = 0; tex.pendingMipGeneration = 0; tex.pendingMipChain.clear(); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; needsRecreate = true; } } if (!needsRecreate) { tex.mipLevels = desiredMipLevels; // Re-write the SRV descriptor so same-resource changes like RGB DXT1 // alpha-forcing or mip-count decisions are reflected without having to // recreate the texture. EnsureSrvDescriptor(); return; } tex.mipLevels = desiredMipLevels; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Width = (UINT)tex.width; rd.Height = (UINT)tex.height; rd.DepthOrArraySize = 1; rd.MipLevels = (UINT16)tex.mipLevels; rd.Format = dxgiFormat; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&tex.texture))); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; EnsureSrvDescriptor(); tex.gpuValid = false; } static TextureResource* QD3D12_EnsureLightingTexture(int width, int height) { const DXGI_FORMAT desiredFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; if (g_lightingTextureId == 0) { g_lightingTextureId = g_gl.nextTextureId++; TextureResource tex{}; tex.glId = g_lightingTextureId; tex.width = (int)width; tex.height = (int)height; tex.format = GL_RGBA; tex.compressed = false; tex.compressedInternalFormat = 0; tex.compressedBlockBytes = 0; tex.compressedImageSize = 0; tex.forceOpaqueAlpha = false; tex.dxgiFormat = desiredFormat; tex.mipLevels = 1; tex.minFilter = GL_LINEAR; tex.magFilter = GL_LINEAR; tex.wrapS = GL_CLAMP; tex.wrapT = GL_CLAMP; tex.srvIndex = g_gl.nextSrvIndex++; tex.srvCpu = QD3D12_SrvCpu(tex.srvIndex); tex.srvGpu = QD3D12_SrvGpu(tex.srvIndex); auto it = g_gl.textures.emplace(g_lightingTextureId, std::move(tex)).first; g_lightingTexture = &it->second; } if (!g_lightingTexture) { auto it = g_gl.textures.find(g_lightingTextureId); if (it == g_gl.textures.end()) return nullptr; g_lightingTexture = &it->second; } g_lightingTexture->dxgiFormat = desiredFormat; g_lightingTexture->mipLevels = 1; const bool needsCreate = !g_lightingTexture->texture || g_lightingTexture->width != (int)width || g_lightingTexture->height != (int)height || g_lightingTexture->texture->GetDesc().Format != desiredFormat || ((g_lightingTexture->texture->GetDesc().Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) == 0); if (!needsCreate) return g_lightingTexture; if (g_lightingTexture->texture) { QD3D12_RetireResource(g_lightingTexture->texture); } g_lightingTexture->width = (int)width; g_lightingTexture->height = (int)height; g_lightingTexture->compressed = false; g_lightingTexture->compressedInternalFormat = 0; g_lightingTexture->compressedBlockBytes = 0; g_lightingTexture->compressedImageSize = 0; g_lightingTexture->forceOpaqueAlpha = false; g_lightingTexture->sysmem.clear(); D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Alignment = 0; rd.Width = width; rd.Height = height; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.Format = desiredFormat; rd.SampleDesc.Count = 1; rd.SampleDesc.Quality = 0; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&g_lightingTexture->texture))); D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srv.Format = rd.Format; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.Texture2D.MipLevels = 1; g_gl.device->CreateShaderResourceView( g_lightingTexture->texture.Get(), &srv, g_lightingTexture->srvCpu); g_lightingTexture->gpuValid = true; g_lightingTexture->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; g_lightingTextureState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; return g_lightingTexture; } static void ConvertToRGBA8(const TextureResource& tex, std::vector& outRGBA) { const int pixelCount = tex.width * tex.height; outRGBA.resize((size_t)pixelCount * 4); if (tex.sysmem.empty()) { std::fill(outRGBA.begin(), outRGBA.end(), 255); return; } const uint8_t* src = tex.sysmem.data(); uint8_t* dst = outRGBA.data(); if (tex.format == GL_RGBA) { memcpy(dst, src, outRGBA.size()); return; } if (tex.format == GL_RGB) { #if defined(__SSSE3__) || (defined(_M_IX86_FP) || defined(_M_X64)) // SSSE3 path: process 4 RGB pixels (12 bytes) -> 16 RGBA bytes at a time. // Output: // [r0 g0 b0 255 r1 g1 b1 255 r2 g2 b2 255 r3 g3 b3 255] // // We load 16 bytes even though we only logically consume 12. That means // the source buffer must have at least 4 readable bytes past the final // 12-byte chunk if you try to run this on the very last block. To keep it // safe, only use SIMD while at least 16 source bytes remain. // // So the SIMD loop runs while (i + 4) <= pixelCount AND enough source bytes // remain for a safe 16-byte load. const __m128i alphaMask = _mm_set1_epi32(0xFF000000); int i = 0; int srcByteOffset = 0; const int srcBytes = pixelCount * 3; // Need 16 readable bytes from src + srcByteOffset while (i + 4 <= pixelCount && srcByteOffset + 16 <= srcBytes) { __m128i in = _mm_loadu_si128(reinterpret_cast(src + srcByteOffset)); // Pull RGB triples into 32-bit lanes: // lane0 = [r0 g0 b0 x] // lane1 = [r1 g1 b1 x] // lane2 = [r2 g2 b2 x] // lane3 = [r3 g3 b3 x] const __m128i shuffled = _mm_shuffle_epi8( in, _mm_setr_epi8( 0, 1, 2, char(0x80), 3, 4, 5, char(0x80), 6, 7, 8, char(0x80), 9, 10, 11, char(0x80) ) ); const __m128i out = _mm_or_si128(shuffled, alphaMask); _mm_storeu_si128(reinterpret_cast<__m128i*>(dst + i * 4), out); i += 4; srcByteOffset += 12; } for (; i < pixelCount; ++i) { dst[i * 4 + 0] = src[i * 3 + 0]; dst[i * 4 + 1] = src[i * 3 + 1]; dst[i * 4 + 2] = src[i * 3 + 2]; dst[i * 4 + 3] = 255; } #else for (int i = 0; i < pixelCount; ++i) { dst[i * 4 + 0] = src[i * 3 + 0]; dst[i * 4 + 1] = src[i * 3 + 1]; dst[i * 4 + 2] = src[i * 3 + 2]; dst[i * 4 + 3] = 255; } #endif return; } if (tex.format == GL_ALPHA) { #if defined(__AVX2__) || defined(_M_X64) int i = 0; // 32 pixels at a time #if defined(__AVX2__) const __m256i white = _mm256_set1_epi32(0x00FFFFFF); for (; i + 32 <= pixelCount; i += 32) { __m256i a = _mm256_loadu_si256(reinterpret_cast(src + i)); __m256i lo16 = _mm256_unpacklo_epi8(_mm256_setzero_si256(), a); __m256i hi16 = _mm256_unpackhi_epi8(_mm256_setzero_si256(), a); __m256i p0 = _mm256_unpacklo_epi16(white, lo16); __m256i p1 = _mm256_unpackhi_epi16(white, lo16); __m256i p2 = _mm256_unpacklo_epi16(white, hi16); __m256i p3 = _mm256_unpackhi_epi16(white, hi16); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 0) * 4), p0); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 8) * 4), p1); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 16) * 4), p2); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 24) * 4), p3); } #endif for (; i < pixelCount; ++i) { dst[i * 4 + 0] = 255; dst[i * 4 + 1] = 255; dst[i * 4 + 2] = 255; dst[i * 4 + 3] = src[i]; } #else for (int i = 0; i < pixelCount; ++i) { dst[i * 4 + 0] = 255; dst[i * 4 + 1] = 255; dst[i * 4 + 2] = 255; dst[i * 4 + 3] = src[i]; } #endif return; } if (tex.format == GL_LUMINANCE || tex.format == GL_INTENSITY) { const bool intensity = (tex.format == GL_INTENSITY); #if defined(__AVX2__) || defined(_M_X64) int i = 0; #if defined(__AVX2__) for (; i + 32 <= pixelCount; i += 32) { __m256i v = _mm256_loadu_si256(reinterpret_cast(src + i)); __m256i lo16 = _mm256_unpacklo_epi8(v, v); __m256i hi16 = _mm256_unpackhi_epi8(v, v); __m256i p0 = _mm256_unpacklo_epi16(lo16, lo16); __m256i p1 = _mm256_unpackhi_epi16(lo16, lo16); __m256i p2 = _mm256_unpacklo_epi16(hi16, hi16); __m256i p3 = _mm256_unpackhi_epi16(hi16, hi16); if (!intensity) { // Force alpha to 255 for luminance const __m256i alphaMask = _mm256_set1_epi32(0xFF000000); const __m256i rgbMask = _mm256_set1_epi32(0x00FFFFFF); p0 = _mm256_or_si256(_mm256_and_si256(p0, rgbMask), alphaMask); p1 = _mm256_or_si256(_mm256_and_si256(p1, rgbMask), alphaMask); p2 = _mm256_or_si256(_mm256_and_si256(p2, rgbMask), alphaMask); p3 = _mm256_or_si256(_mm256_and_si256(p3, rgbMask), alphaMask); } _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 0) * 4), p0); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 8) * 4), p1); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 16) * 4), p2); _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + (i + 24) * 4), p3); } #endif for (; i < pixelCount; ++i) { const uint8_t v = src[i]; dst[i * 4 + 0] = v; dst[i * 4 + 1] = v; dst[i * 4 + 2] = v; dst[i * 4 + 3] = intensity ? v : 255; } #else for (int i = 0; i < pixelCount; ++i) { const uint8_t v = src[i]; dst[i * 4 + 0] = v; dst[i * 4 + 1] = v; dst[i * 4 + 2] = v; dst[i * 4 + 3] = intensity ? v : 255; } #endif return; } std::fill(outRGBA.begin(), outRGBA.end(), 255); } static inline float QD3D12_Srgb8ToLinearFloat(uint8_t v) { const float c = (float)v * (1.0f / 255.0f); return (c <= 0.04045f) ? (c * (1.0f / 12.92f)) : powf((c + 0.055f) * (1.0f / 1.055f), 2.4f); } static inline uint8_t QD3D12_LinearFloatToSrgb8(float v) { v = ClampValue(v, 0.0f, 1.0f); const float c = (v <= 0.0031308f) ? (v * 12.92f) : (1.055f * powf(v, 1.0f / 2.4f) - 0.055f); return (uint8_t)ClampValue((int)floorf(c * 255.0f + 0.5f), 0, 255); } static inline const uint8_t* QD3D12_RGBA8PixelClamp(const std::vector& img, UINT w, UINT h, int x, int y) { const UINT cx = ClampValue((UINT)max(0, x), 0u, w - 1u); const UINT cy = ClampValue((UINT)max(0, y), 0u, h - 1u); return img.data() + ((size_t)cy * (size_t)w + (size_t)cx) * 4u; } static void QD3D12_FilterMipPixelGammaAware( const std::vector& src, UINT srcW, UINT srcH, UINT dstX, UINT dstY, uint8_t* out) { // Centered tent-ish downsample with a mild unsharp restore. This is less blurry // than a plain 2x2 box, but still stable enough for old noisy diffuse textures. const int baseX = (int)dstX * 2; const int baseY = (int)dstY * 2; static const float kWeights[4][4] = { { 1.0f, 2.0f, 2.0f, 1.0f }, { 2.0f, 4.0f, 4.0f, 2.0f }, { 2.0f, 4.0f, 4.0f, 2.0f }, { 1.0f, 2.0f, 2.0f, 1.0f } }; float rgbLin[3] = { 0.0f, 0.0f, 0.0f }; float alpha = 0.0f; float weightSum = 0.0f; for (int ky = 0; ky < 4; ++ky) { for (int kx = 0; kx < 4; ++kx) { const float w = kWeights[ky][kx]; const uint8_t* p = QD3D12_RGBA8PixelClamp(src, srcW, srcH, baseX + kx - 1, baseY + ky - 1); const float a = (float)p[3] * (1.0f / 255.0f); // Premultiply for alpha-tested/cutout textures so transparent border colors // do not bleed into smaller mips. rgbLin[0] += QD3D12_Srgb8ToLinearFloat(p[0]) * a * w; rgbLin[1] += QD3D12_Srgb8ToLinearFloat(p[1]) * a * w; rgbLin[2] += QD3D12_Srgb8ToLinearFloat(p[2]) * a * w; alpha += a * w; weightSum += w; } } float outA = alpha / max(weightSum, 1.0e-6f); float invAlpha = (outA > 1.0e-5f) ? (1.0f / (outA * weightSum)) : (1.0f / weightSum); float filtered[3] = { rgbLin[0] * invAlpha, rgbLin[1] * invAlpha, rgbLin[2] * invAlpha }; // Mild detail restore from the center 2x2 average. This counteracts the tent // filter's softness without turning distant mips into shimmering garbage. float center[3] = { 0.0f, 0.0f, 0.0f }; float centerA = 0.0f; for (int yy = 0; yy < 2; ++yy) { for (int xx = 0; xx < 2; ++xx) { const uint8_t* p = QD3D12_RGBA8PixelClamp(src, srcW, srcH, baseX + xx, baseY + yy); const float a = (float)p[3] * (1.0f / 255.0f); center[0] += QD3D12_Srgb8ToLinearFloat(p[0]) * a; center[1] += QD3D12_Srgb8ToLinearFloat(p[1]) * a; center[2] += QD3D12_Srgb8ToLinearFloat(p[2]) * a; centerA += a; } } if (centerA > 1.0e-5f) { center[0] /= centerA; center[1] /= centerA; center[2] /= centerA; const float sharpen = 1.0f; filtered[0] = ClampValue(filtered[0] + (center[0] - filtered[0]) * sharpen, 0.0f, 1.0f); filtered[1] = ClampValue(filtered[1] + (center[1] - filtered[1]) * sharpen, 0.0f, 1.0f); filtered[2] = ClampValue(filtered[2] + (center[2] - filtered[2]) * sharpen, 0.0f, 1.0f); } out[0] = QD3D12_LinearFloatToSrgb8(filtered[0]); out[1] = QD3D12_LinearFloatToSrgb8(filtered[1]); out[2] = QD3D12_LinearFloatToSrgb8(filtered[2]); out[3] = (uint8_t)ClampValue((int)floorf(outA * 255.0f + 0.5f), 0, 255); } static void QD3D12_BuildRGBA8MipChain(const TextureResource& tex, std::vector>& outMips) { outMips.clear(); if (tex.width <= 0 || tex.height <= 0) return; ConvertToRGBA8(tex, outMips.emplace_back()); UINT srcW = (UINT)tex.width; UINT srcH = (UINT)tex.height; const UINT mipCount = QD3D12_CalcMipCount(tex.width, tex.height); outMips.reserve(mipCount); for (UINT level = 1; level < mipCount; ++level) { const UINT dstW = std::max(1u, srcW >> 1); const UINT dstH = std::max(1u, srcH >> 1); const std::vector& src = outMips[level - 1]; std::vector& dst = outMips.emplace_back(); dst.resize((size_t)dstW * (size_t)dstH * 4u); for (UINT y = 0; y < dstH; ++y) { for (UINT x = 0; x < dstW; ++x) { uint8_t* d = dst.data() + ((size_t)y * dstW + x) * 4u; QD3D12_FilterMipPixelGammaAware(src, srcW, srcH, x, y, d); } } srcW = dstW; srcH = dstH; } } struct QD3D12MipBuildInput { GLuint textureId = 0; uint64_t generation = 0; int width = 0; int height = 0; GLenum format = GL_RGBA; std::vector sysmem; }; struct QD3D12MipBuildResult { GLuint textureId = 0; uint64_t generation = 0; int width = 0; int height = 0; GLenum format = GL_RGBA; std::vector> mipChain; }; static std::mutex g_qd3d12MipMutex; static std::condition_variable g_qd3d12MipCv; static std::deque g_qd3d12MipJobs; static std::deque g_qd3d12MipCompleted; static std::vector g_qd3d12MipWorkers; static bool g_qd3d12MipShutdown = false; static bool QD3D12_TextureShouldBuildAsyncMips(const TextureResource& tex) { if (tex.compressed || tex.width <= 0 || tex.height <= 0 || tex.sysmem.empty()) return false; if (QD3D12_DesiredTextureMipLevels(tex) <= 1) return false; return tex.mipChainRequested || QD3D12_TextureWantsMipSampling(tex); } static void QD3D12_MipWorkerMain() { for (;;) { QD3D12MipBuildInput input; { std::unique_lock lock(g_qd3d12MipMutex); g_qd3d12MipCv.wait(lock, []() { return g_qd3d12MipShutdown || !g_qd3d12MipJobs.empty(); }); if (g_qd3d12MipShutdown && g_qd3d12MipJobs.empty()) return; input = std::move(g_qd3d12MipJobs.front()); g_qd3d12MipJobs.pop_front(); } TextureResource snapshot{}; snapshot.glId = input.textureId; snapshot.width = input.width; snapshot.height = input.height; snapshot.format = input.format; snapshot.compressed = false; snapshot.dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; snapshot.sysmem = std::move(input.sysmem); QD3D12MipBuildResult result{}; result.textureId = input.textureId; result.generation = input.generation; result.width = input.width; result.height = input.height; result.format = input.format; QD3D12_BuildRGBA8MipChain(snapshot, result.mipChain); { std::lock_guard lock(g_qd3d12MipMutex); if (!g_qd3d12MipShutdown) g_qd3d12MipCompleted.push_back(std::move(result)); } } } static void QD3D12_EnsureMipWorkersStarted() { if (!g_qd3d12MipWorkers.empty()) return; g_qd3d12MipShutdown = false; UINT workerCount = 1; const unsigned hw = std::thread::hardware_concurrency(); if (hw >= 6) workerCount = 2; for (UINT i = 0; i < workerCount; ++i) g_qd3d12MipWorkers.emplace_back(QD3D12_MipWorkerMain); } static void QD3D12_ShutdownMipWorkers() { { std::lock_guard lock(g_qd3d12MipMutex); g_qd3d12MipShutdown = true; g_qd3d12MipJobs.clear(); g_qd3d12MipCompleted.clear(); } g_qd3d12MipCv.notify_all(); for (std::thread& worker : g_qd3d12MipWorkers) { if (worker.joinable()) worker.join(); } g_qd3d12MipWorkers.clear(); g_qd3d12MipShutdown = false; } static void QD3D12_InvalidateTextureMipChain(TextureResource& tex, bool invalidateBase) { ++tex.cpuGeneration; if (tex.cpuGeneration == 0) ++tex.cpuGeneration; tex.mipChainResident = false; tex.mipChainResidentGeneration = 0; tex.mipJobQueuedGeneration = 0; tex.pendingMipGeneration = 0; tex.pendingMipChain.clear(); if (invalidateBase) tex.gpuValid = false; } static void QD3D12_RequestAsyncMipBuild(TextureResource& tex) { if (!QD3D12_TextureShouldBuildAsyncMips(tex)) return; if (tex.mipChainResident && tex.mipChainResidentGeneration == tex.cpuGeneration) return; if (tex.mipJobQueuedGeneration == tex.cpuGeneration) return; QD3D12_EnsureMipWorkersStarted(); QD3D12MipBuildInput input{}; input.textureId = tex.glId; input.generation = tex.cpuGeneration; input.width = tex.width; input.height = tex.height; input.format = tex.format; input.sysmem = tex.sysmem; { std::lock_guard lock(g_qd3d12MipMutex); if (!g_qd3d12MipShutdown) { g_qd3d12MipJobs.push_back(std::move(input)); tex.mipJobQueuedGeneration = tex.cpuGeneration; } } g_qd3d12MipCv.notify_one(); } static UINT64 QD3D12_EstimateMipUploadBytes(const std::vector>& mipChain) { UINT64 bytes = 0; for (size_t i = 1; i < mipChain.size(); ++i) bytes += (UINT64)mipChain[i].size(); return bytes; } static bool QD3D12_UploadReadyMipChain(TextureResource& tex, std::vector>& mipChain, uint64_t generation) { if (!g_currentWindow || !g_gl.cmdList || !tex.texture) return false; if (generation != tex.cpuGeneration || mipChain.size() <= 1) return true; if (tex.compressed || tex.width <= 0 || tex.height <= 0) return true; if (!tex.gpuValid) return false; EnsureTextureResource(tex); if (!tex.gpuValid) return false; if (!tex.texture || tex.texture->GetDesc().MipLevels <= 1) return false; const UINT expectedMipLevels = std::min(tex.mipLevels, (UINT)mipChain.size()); if (expectedMipLevels <= 1) return true; if (tex.state != D3D12_RESOURCE_STATE_COPY_DEST) { D3D12_RESOURCE_BARRIER toCopy{}; toCopy.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toCopy.Transition.pResource = tex.texture.Get(); 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); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; } UINT mipW = std::max(1u, (UINT)tex.width >> 1); UINT mipH = std::max(1u, (UINT)tex.height >> 1); for (UINT mip = 1; mip < expectedMipLevels; ++mip) { const std::vector& mipData = mipChain[mip]; const UINT srcRowBytes = mipW * 4u; const UINT rowPitch = (srcRowBytes + 255u) & ~255u; const UINT uploadSize = rowPitch * mipH; UploadAlloc alloc = QD3D12_AllocUpload(uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); uint8_t* dstBase = (uint8_t*)alloc.cpu; memset(dstBase, 0, uploadSize); for (UINT y = 0; y < mipH; ++y) { memcpy(dstBase + (size_t)y * rowPitch, mipData.data() + (size_t)y * srcRowBytes, srcRowBytes); } D3D12_TEXTURE_COPY_LOCATION srcLoc{}; srcLoc.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(); srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcLoc.PlacedFootprint.Offset = alloc.offset; srcLoc.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srcLoc.PlacedFootprint.Footprint.Width = mipW; srcLoc.PlacedFootprint.Footprint.Height = mipH; srcLoc.PlacedFootprint.Footprint.Depth = 1; srcLoc.PlacedFootprint.Footprint.RowPitch = rowPitch; D3D12_TEXTURE_COPY_LOCATION dstLoc{}; dstLoc.pResource = tex.texture.Get(); dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLoc.SubresourceIndex = mip; g_gl.cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); mipW = std::max(1u, mipW >> 1); mipH = std::max(1u, mipH >> 1); } 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.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &toSrv); tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; tex.mipChainResident = true; tex.mipChainResidentGeneration = generation; tex.pendingMipGeneration = 0; tex.pendingMipChain.clear(); EnsureTextureResource(tex); return true; } static void QD3D12_ProcessCompletedTextureMipJobs(UINT maxUploads) { if (!g_currentWindow || !g_gl.cmdList || maxUploads == 0) return; std::deque completed; { std::lock_guard lock(g_qd3d12MipMutex); completed.swap(g_qd3d12MipCompleted); } for (QD3D12MipBuildResult& result : completed) { auto it = g_gl.textures.find(result.textureId); if (it == g_gl.textures.end()) continue; TextureResource& tex = it->second; if (result.generation != tex.cpuGeneration || result.width != tex.width || result.height != tex.height || result.format != tex.format) continue; tex.pendingMipGeneration = result.generation; tex.pendingMipChain = std::move(result.mipChain); } UINT uploads = 0; const UINT64 uploadBudgetBytes = 8ull * 1024ull * 1024ull; UINT64 uploadedBytes = 0; for (auto& kv : g_gl.textures) { if (uploads >= maxUploads) break; TextureResource& tex = kv.second; if (tex.pendingMipChain.empty() || tex.pendingMipGeneration != tex.cpuGeneration) continue; const UINT64 bytes = QD3D12_EstimateMipUploadBytes(tex.pendingMipChain); if (uploads > 0 && uploadedBytes + bytes > uploadBudgetBytes) break; if (QD3D12_UploadReadyMipChain(tex, tex.pendingMipChain, tex.pendingMipGeneration)) { ++uploads; uploadedBytes += bytes; } } } static void UploadTexture(TextureResource& tex) { if (!tex.texture) return; if (tex.compressed) { const UINT blockBytes = tex.compressedBlockBytes ? tex.compressedBlockBytes : QD3D12_CompressedTextureBlockBytes(tex.compressedInternalFormat); if (blockBytes == 0 || tex.dxgiFormat == DXGI_FORMAT_UNKNOWN || tex.width <= 0 || tex.height <= 0) { g_gl.lastError = GL_INVALID_ENUM; return; } const UINT blocksWide = QD3D12_CompressedTextureBlocksWide(tex.width); const UINT blocksHigh = QD3D12_CompressedTextureBlocksHigh(tex.height); const UINT srcRowBytes = blocksWide * blockBytes; const UINT rowPitch = (srcRowBytes + 255u) & ~255u; const UINT uploadSize = rowPitch * blocksHigh; UploadAlloc alloc = QD3D12_AllocUpload(uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); uint8_t* dstBase = (uint8_t*)alloc.cpu; memset(dstBase, 0, uploadSize); if (!tex.sysmem.empty() && blockBytes != 0) { const uint8_t* srcBase = tex.sysmem.data(); const size_t available = tex.sysmem.size(); for (UINT row = 0; row < blocksHigh; ++row) { const size_t srcOff = (size_t)row * (size_t)srcRowBytes; if (srcOff >= available) break; const size_t copyBytes = std::min((size_t)srcRowBytes, available - srcOff); memcpy(dstBase + (size_t)row * (size_t)rowPitch, srcBase + srcOff, copyBytes); } } if (tex.state != D3D12_RESOURCE_STATE_COPY_DEST) { D3D12_RESOURCE_BARRIER toCopy{}; toCopy.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toCopy.Transition.pResource = tex.texture.Get(); 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); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; } D3D12_TEXTURE_COPY_LOCATION srcLoc{}; srcLoc.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(); srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcLoc.PlacedFootprint.Offset = alloc.offset; srcLoc.PlacedFootprint.Footprint.Format = tex.dxgiFormat; srcLoc.PlacedFootprint.Footprint.Width = tex.width; srcLoc.PlacedFootprint.Footprint.Height = tex.height; srcLoc.PlacedFootprint.Footprint.Depth = 1; srcLoc.PlacedFootprint.Footprint.RowPitch = rowPitch; D3D12_TEXTURE_COPY_LOCATION dstLoc{}; 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); 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.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &toSrv); tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; tex.gpuValid = true; return; } std::vector baseRGBA; ConvertToRGBA8(tex, baseRGBA); if (baseRGBA.empty()) return; if (tex.state != D3D12_RESOURCE_STATE_COPY_DEST) { D3D12_RESOURCE_BARRIER toCopy{}; toCopy.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; toCopy.Transition.pResource = tex.texture.Get(); 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); tex.state = D3D12_RESOURCE_STATE_COPY_DEST; } const UINT srcRowBytes = (UINT)tex.width * 4u; const UINT rowPitch = (srcRowBytes + 255u) & ~255u; const UINT uploadSize = rowPitch * (UINT)tex.height; UploadAlloc alloc = QD3D12_AllocUpload(uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); uint8_t* dstBase = (uint8_t*)alloc.cpu; memset(dstBase, 0, uploadSize); for (UINT y = 0; y < (UINT)tex.height; ++y) { memcpy(dstBase + (size_t)y * rowPitch, baseRGBA.data() + (size_t)y * srcRowBytes, srcRowBytes); } D3D12_TEXTURE_COPY_LOCATION srcLoc{}; srcLoc.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get(); srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcLoc.PlacedFootprint.Offset = alloc.offset; srcLoc.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srcLoc.PlacedFootprint.Footprint.Width = tex.width; srcLoc.PlacedFootprint.Footprint.Height = tex.height; srcLoc.PlacedFootprint.Footprint.Depth = 1; srcLoc.PlacedFootprint.Footprint.RowPitch = rowPitch; D3D12_TEXTURE_COPY_LOCATION dstLoc{}; 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); 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.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; g_gl.cmdList->ResourceBarrier(1, &toSrv); tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; tex.gpuValid = true; EnsureTextureResource(tex); QD3D12_RequestAsyncMipBuild(tex); } static GLenum QD3D12_NormalizeCopyTextureFormat(GLenum format) { switch (format) { case 1: return GL_LUMINANCE; case 3: return GL_RGB; case 4: return GL_RGBA; case GL_ALPHA: case GL_LUMINANCE: case GL_INTENSITY: case GL_RGB: case GL_RGBA: return format; default: return GL_RGBA; } } static void QD3D12_PackRGBA8ToTextureFormat(const uint8_t* srcRGBA, int pixelCount, GLenum dstFormat, std::vector& out) { dstFormat = QD3D12_NormalizeCopyTextureFormat(dstFormat); switch (dstFormat) { case GL_RGB: out.resize((size_t)pixelCount * 3); for (int i = 0; i < pixelCount; ++i) { out[(size_t)i * 3 + 0] = srcRGBA[(size_t)i * 4 + 0]; out[(size_t)i * 3 + 1] = srcRGBA[(size_t)i * 4 + 1]; out[(size_t)i * 3 + 2] = srcRGBA[(size_t)i * 4 + 2]; } break; case GL_ALPHA: out.resize((size_t)pixelCount); for (int i = 0; i < pixelCount; ++i) { out[(size_t)i] = srcRGBA[(size_t)i * 4 + 3]; } break; case GL_LUMINANCE: case GL_INTENSITY: out.resize((size_t)pixelCount); for (int i = 0; i < pixelCount; ++i) { const uint8_t r = srcRGBA[(size_t)i * 4 + 0]; const uint8_t g = srcRGBA[(size_t)i * 4 + 1]; const uint8_t b = srcRGBA[(size_t)i * 4 + 2]; out[(size_t)i] = (uint8_t)((77u * r + 150u * g + 29u * b + 128u) >> 8); } break; case GL_RGBA: default: out.assign(srcRGBA, srcRGBA + (size_t)pixelCount * 4); break; } } static bool QD3D12_ReadFramebufferRegionRGBA8(GLint x, GLint y, GLsizei width, GLsizei height, std::vector& outRGBA) { outRGBA.clear(); if (!g_currentWindow || !g_gl.device || !g_gl.queue || !g_gl.cmdList) return false; if (width <= 0 || height <= 0) return false; QD3D12_EnsureFrameOpen(); QD3D12_FlushQueuedBatches(); QD3D12Window& w = *g_currentWindow; ID3D12Resource* srcResource = nullptr; D3D12_RESOURCE_STATES* trackedState = nullptr; UINT srcWidth = 0; UINT srcHeight = 0; const bool wantNativeBuffer = (g_gl.readBuffer == GL_FRONT) || (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE) || g_gl.sceneResolvedThisFrame; if (!wantNativeBuffer) QD3D12_ResolveGBufferForCurrentFrame(w); if (wantNativeBuffer) { srcResource = w.backBuffers[w.frameIndex].Get(); trackedState = &w.backBufferState[w.frameIndex]; srcWidth = w.width; srcHeight = w.height; } else { srcResource = w.sceneColorBuffers[w.frameIndex].Get(); trackedState = &w.sceneColorState[w.frameIndex]; srcWidth = w.renderWidth; srcHeight = w.renderHeight; } if (!srcResource || !trackedState || srcWidth == 0 || srcHeight == 0) return false; if (x < 0 || y < 0 || (UINT)(x + width) > srcWidth || (UINT)(y + height) > srcHeight) return false; const UINT srcLeft = (UINT)x; const UINT srcTop = srcHeight - (UINT)(y + height); const UINT copyWidth = (UINT)width; const UINT copyHeight = (UINT)height; const UINT rowPitch = (copyWidth * 4u + 255u) & ~255u; const UINT64 readbackBytes = (UINT64)rowPitch * (UINT64)copyHeight; ComPtr readback; D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_READBACK; D3D12_RESOURCE_DESC rd{}; rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; rd.Width = readbackBytes; rd.Height = 1; rd.DepthOrArraySize = 1; rd.MipLevels = 1; rd.SampleDesc.Count = 1; rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; QD3D12_CHECK(g_gl.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&readback))); const D3D12_RESOURCE_STATES originalState = *trackedState; QD3D12_TransitionResource(g_gl.cmdList.Get(), srcResource, *trackedState, D3D12_RESOURCE_STATE_COPY_SOURCE); D3D12_TEXTURE_COPY_LOCATION srcLoc{}; srcLoc.pResource = srcResource; srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; srcLoc.SubresourceIndex = 0; D3D12_TEXTURE_COPY_LOCATION dstLoc{}; dstLoc.pResource = readback.Get(); dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dstLoc.PlacedFootprint.Offset = 0; dstLoc.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; dstLoc.PlacedFootprint.Footprint.Width = copyWidth; dstLoc.PlacedFootprint.Footprint.Height = copyHeight; dstLoc.PlacedFootprint.Footprint.Depth = 1; dstLoc.PlacedFootprint.Footprint.RowPitch = rowPitch; D3D12_BOX srcBox{}; srcBox.left = srcLeft; srcBox.top = srcTop; srcBox.front = 0; srcBox.right = srcLeft + copyWidth; srcBox.bottom = srcTop + copyHeight; srcBox.back = 1; g_gl.cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, &srcBox); QD3D12_TransitionResource(g_gl.cmdList.Get(), srcResource, *trackedState, originalState); QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } outRGBA.resize((size_t)copyWidth * (size_t)copyHeight * 4); void* mapped = nullptr; QD3D12_CHECK(readback->Map(0, nullptr, &mapped)); const uint8_t* srcBytes = (const uint8_t*)mapped; for (UINT row = 0; row < copyHeight; ++row) { memcpy(outRGBA.data() + (size_t)row * (size_t)copyWidth * 4, srcBytes + (size_t)row * rowPitch, (size_t)copyWidth * 4); } readback->Unmap(0, nullptr); FrameResources& fr = w.frames[w.frameIndex]; QD3D12_CHECK(fr.cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(fr.cmdAlloc.Get(), nullptr)); QD3D12_BindTargetsForCurrentPhase(w); return true; } static void QD3D12_CopyRGBARegionIntoTexture(TextureResource& tex, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, const uint8_t* rgbaBytes) { if (width <= 0 || height <= 0 || !rgbaBytes) return; if (tex.compressed) { g_gl.lastError = GL_INVALID_OPERATION; return; } tex.format = QD3D12_NormalizeCopyTextureFormat(tex.format); tex.compressed = false; tex.compressedInternalFormat = 0; tex.compressedBlockBytes = 0; tex.compressedImageSize = 0; tex.forceOpaqueAlpha = false; tex.dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; tex.mipLevels = QD3D12_CalcMipCount(tex.width, tex.height); const int bpp = BytesPerPixel(tex.format, GL_UNSIGNED_BYTE); if (bpp <= 0 || tex.width <= 0 || tex.height <= 0) return; const size_t needed = (size_t)tex.width * (size_t)tex.height * (size_t)bpp; if (tex.sysmem.size() != needed) tex.sysmem.resize(needed, 0); std::vector packed; QD3D12_PackRGBA8ToTextureFormat(rgbaBytes, width * height, tex.format, packed); for (int row = 0; row < height; ++row) { const size_t dstOff = ((size_t)(yoffset + row) * (size_t)tex.width + (size_t)xoffset) * (size_t)bpp; const size_t srcOff = (size_t)row * (size_t)width * (size_t)bpp; memcpy(tex.sysmem.data() + dstOff, packed.data() + srcOff, (size_t)width * (size_t)bpp); } QD3D12_InvalidateTextureMipChain(tex, true); } static PipelineMode PickPipeline(bool useTex0, bool useTex1) { const bool textured = useTex0 || useTex1; if (QD3D12_CurrentDrawUsesAlphaBlend()) return textured ? PIPE_BLEND_TEX : PIPE_BLEND_UNTEX; if (g_gl.alphaTest) return textured ? PIPE_ALPHA_TEST_TEX : PIPE_OPAQUE_UNTEX; return textured ? PIPE_OPAQUE_TEX : PIPE_OPAQUE_UNTEX; } static void SetDynamicFixedFunctionState(ID3D12GraphicsCommandList* cl) { cl->SetGraphicsRootSignature(g_gl.rootSig.Get()); ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(1, heaps); cl->RSSetViewports(1, &g_currentWindow->viewport); cl->RSSetScissorRects(1, &g_currentWindow->scissor); } static Mat4 CurrentMVP() { return Mat4::Multiply(g_gl.projStack.back(), g_gl.modelStack.back()); } void glLoadModelMatrixf(const float* m16) { Mat4 m = Mat4::Identity(); if (m16) { memcpy(m.m, m16, sizeof(float) * 16); } memcpy(g_gl.modelMatrix.m, m.m, sizeof(g_gl.modelMatrix.m)); } void APIENTRY glMultMatrixf(const GLfloat* m) { if (!m) return; Mat4 rhs{}; memcpy(rhs.m, m, sizeof(rhs.m)); auto& top = QD3D12_CurrentMatrixStack().back(); top = Mat4::Multiply(top, rhs); } static Mat4 CurrentModelMatrix() { return g_gl.modelMatrix; } static inline void AppendVerticesFast(std::vector& dst, const std::vector& src) { if (src.empty()) return; const size_t oldSize = dst.size(); const size_t addCount = src.size(); dst.resize(oldSize + addCount); memcpy(dst.data() + oldSize, src.data(), addCount * sizeof(GLVertex)); } static void FlushImmediate(GLenum mode, const GLVertex* src, size_t n) { if (!src || n == 0) return; QD3D12_EnsureFrameOpen(); const bool arbProgramsActive = QD3D12ARB_IsActive(); const bool useTex0 = arbProgramsActive ? (g_gl.boundTexture[0] != 0) : g_gl.texture2D[0]; const bool useTex1 = arbProgramsActive ? (g_gl.boundTexture[1] != 0) : g_gl.texture2D[1]; TextureResource* boundTextures[QD3D12_MaxTextureUnits] = {}; for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) boundTextures[unit] = &g_gl.whiteTexture; for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { const bool arbUnit = arbProgramsActive && (g_gl.boundTexture[unit] != 0); const bool fixedColorUnit = (!arbProgramsActive) && (g_gl.texture2D[unit] && unit < 2); const bool taggedNormalUnit = (!arbProgramsActive) && QD3D12_IsTextureTaggedNormalMap(g_gl.boundTexture[unit]); 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 taggedSpecularUnit = (!arbProgramsActive) && QD3D12_IsTextureTaggedSpecularMap(g_gl.boundTexture[unit]); const bool explicitSpecularUnit = (!arbProgramsActive) && (g_gl.currentSpecularMapTexture != 0) && (g_gl.boundTexture[unit] == g_gl.currentSpecularMapTexture); const bool wantsUnit = arbUnit || fixedColorUnit || taggedNormalUnit || explicitNormalUnit || taggedGlowUnit || explicitGlowUnit || taggedSpecularUnit || explicitSpecularUnit; if (!wantsUnit) continue; TextureResource* tex = QD3D12_FindTextureResource(g_gl.boundTexture[unit]); if (tex) boundTextures[unit] = tex; } for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { TextureResource* tex = boundTextures[unit]; if (tex && tex != &g_gl.whiteTexture && !tex->gpuValid) { EnsureTextureResource(*tex); UploadTexture(*tex); } } TextureResource* normalMapTex = QD3D12_SelectNormalMapTexture(boundTextures); if (normalMapTex && normalMapTex != &g_gl.whiteTexture && !normalMapTex->gpuValid) { EnsureTextureResource(*normalMapTex); UploadTexture(*normalMapTex); } if (normalMapTex && normalMapTex != &g_gl.whiteTexture && QD3D12_TextureHasNeuralPOMData(*normalMapTex)) { QD3D12_UploadNeuralPOM(*normalMapTex); } if (g_gl.currentNeuralPOMTexture != 0) { TextureResource* explicitNeural = QD3D12_FindTextureResource(g_gl.currentNeuralPOMTexture); if (explicitNeural && QD3D12_TextureHasNeuralPOMData(*explicitNeural)) QD3D12_UploadNeuralPOM(*explicitNeural); } // Neural payloads can live on the diffuse texture, the normal-map texture, or // any explicitly bound material texture. Upload any bound payload before the // batch key is built so BuildCurrentBatchKey can select it immediately. for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { TextureResource* neuralCandidate = boundTextures[unit]; if (neuralCandidate && neuralCandidate != &g_gl.whiteTexture && QD3D12_TextureHasNeuralPOMData(*neuralCandidate)) QD3D12_UploadNeuralPOM(*neuralCandidate); } TextureResource* glowMapTex = QD3D12_SelectGlowMapTexture(boundTextures); if (glowMapTex && glowMapTex != &g_gl.whiteTexture && !glowMapTex->gpuValid) { EnsureTextureResource(*glowMapTex); UploadTexture(*glowMapTex); } TextureResource* specularMapTex = QD3D12_SelectSpecularMapTexture(boundTextures); if (specularMapTex && specularMapTex != &g_gl.whiteTexture && !specularMapTex->gpuValid) { EnsureTextureResource(*specularMapTex); UploadTexture(*specularMapTex); } TextureResource* tex0 = boundTextures[0]; TextureResource* tex1 = boundTextures[1]; BatchKey key = BuildCurrentBatchKey(mode, tex0, tex1, boundTextures); if (key.useTessellation && !QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(mode, n)) key.useTessellation = false; const size_t markerCursor = g_gl.queryMarkers.size(); QueuedBatch* batch = nullptr; if (!g_gl.queuedBatches.empty() && BatchKeyEquals(g_gl.queuedBatches.back().key, key) && g_gl.queuedBatches.back().markerEnd == markerCursor) { batch = &g_gl.queuedBatches.back(); } else { QueuedBatch newBatch{}; newBatch.key = key; newBatch.markerBegin = markerCursor; newBatch.markerEnd = markerCursor; newBatch.firstVertex = 0; newBatch.vertexCount = 0; g_gl.queuedBatches.push_back(newBatch); batch = &g_gl.queuedBatches.back(); } auto AppendToBatch = [&](size_t outCount) -> GLVertex* { if (outCount == 0) return nullptr; size_t first = 0; GLVertex* dst = QD3D12_AllocFrameVertices(outCount, &first); if (!dst) return nullptr; if (batch->vertexCount == 0) { batch->firstVertex = first; batch->vertexCount = outCount; } else { // Since arena is linear, merged batches must remain contiguous. assert(batch->firstVertex + batch->vertexCount == first); batch->vertexCount += outCount; } return dst; }; switch (mode) { case GL_TRIANGLES: case GL_POINTS: { GLVertex* out = AppendToBatch(n); if (!out) return; memcpy(out, src, n * sizeof(GLVertex)); return; } case GL_LINES: { const size_t segCount = n >> 1; const size_t outCount = segCount * 2; GLVertex* out = AppendToBatch(outCount); if (!out) return; for (size_t i = 0, d = 0; i + 1 < n; i += 2, d += 2) { out[d + 0] = src[i + 0]; out[d + 1] = src[i + 1]; } return; } case GL_LINE_STRIP: { if (n < 2) return; const size_t outCount = (n - 1) * 2; GLVertex* out = AppendToBatch(outCount); if (!out) return; for (size_t i = 1, d = 0; i < n; ++i, d += 2) { out[d + 0] = src[i - 1]; out[d + 1] = src[i]; } return; } case GL_LINE_LOOP: { if (n < 2) return; const size_t outCount = n * 2; GLVertex* out = AppendToBatch(outCount); if (!out) return; size_t d = 0; for (size_t i = 1; i < n; ++i, d += 2) { out[d + 0] = src[i - 1]; out[d + 1] = src[i]; } out[d + 0] = src[n - 1]; out[d + 1] = src[0]; return; } case GL_TRIANGLE_STRIP: { if (n < 3) return; const size_t outCount = (n - 2) * 3; GLVertex* out = AppendToBatch(outCount); if (!out) return; size_t d = 0; for (size_t i = 2; i < n; ++i, d += 3) { if ((i & 1) == 0) { out[d + 0] = src[i - 2]; out[d + 1] = src[i - 1]; out[d + 2] = src[i]; } else { out[d + 0] = src[i - 1]; out[d + 1] = src[i - 2]; out[d + 2] = src[i]; } } return; } case GL_TRIANGLE_FAN: { if (n < 3) return; const size_t outCount = (n - 2) * 3; GLVertex* out = AppendToBatch(outCount); if (!out) return; const GLVertex v0 = src[0]; size_t d = 0; for (size_t i = 2; i < n; ++i, d += 3) { out[d + 0] = v0; out[d + 1] = src[i - 1]; out[d + 2] = src[i]; } return; } case GL_QUADS: { const size_t quadCount = n >> 2; const size_t outCount = quadCount * 6; GLVertex* out = AppendToBatch(outCount); if (!out) return; size_t d = 0; for (size_t i = 0; i + 3 < n; i += 4, d += 6) { const GLVertex& v0 = src[i + 0]; const GLVertex& v1 = src[i + 1]; const GLVertex& v2 = src[i + 2]; const GLVertex& v3 = src[i + 3]; out[d + 0] = v0; out[d + 1] = v1; out[d + 2] = v2; out[d + 3] = v0; out[d + 4] = v2; out[d + 5] = v3; } return; } case GL_QUAD_STRIP: { if (n < 4) return; const size_t quadCount = (n - 2) >> 1; const size_t outCount = quadCount * 6; GLVertex* out = AppendToBatch(outCount); if (!out) return; size_t d = 0; for (size_t i = 0; i + 3 < n; i += 2, d += 6) { const GLVertex& v0 = src[i + 0]; const GLVertex& v1 = src[i + 1]; const GLVertex& v2 = src[i + 2]; const GLVertex& v3 = src[i + 3]; out[d + 0] = v0; out[d + 1] = v1; out[d + 2] = v2; out[d + 3] = v2; out[d + 4] = v1; out[d + 5] = v3; } return; } case GL_POLYGON: { // Keep this path separate for now. // Best next step is tessellating directly into the arena too. std::vector tess; tess.reserve(n); std::vector temp(src, src + n); TessellatePolygon(temp, tess); if (!tess.empty()) { GLVertex* out = AppendToBatch(tess.size()); if (!out) return; memcpy(out, tess.data(), tess.size() * sizeof(GLVertex)); } return; } default: assert(!"Unknown FlushImmediate type!"); return; } } static void QD3D12_EmitQueryMarkers(size_t beginIdx, size_t endIdx) { for (size_t i = beginIdx; i < endIdx; ++i) { const QueryMarker& m = g_gl.queryMarkers[i]; auto it = g_gl.queries.find(m.id); if (it == g_gl.queries.end()) continue; GLOcclusionQuery& q = it->second; if (q.heapIndex == UINT_MAX) continue; if (m.type == QueryMarker::Begin) { g_gl.cmdList->BeginQuery(g_gl.occlusionQueryHeap.Get(), D3D12_QUERY_TYPE_OCCLUSION, q.heapIndex); } else { g_gl.cmdList->EndQuery(g_gl.occlusionQueryHeap.Get(), D3D12_QUERY_TYPE_OCCLUSION, q.heapIndex); g_gl.cmdList->ResolveQueryData( g_gl.occlusionQueryHeap.Get(), D3D12_QUERY_TYPE_OCCLUSION, q.heapIndex, 1, g_gl.occlusionReadback.Get(), sizeof(UINT64) * q.heapIndex); q.submittedFence = QD3D12_CurrentSubmissionFenceValue(); } } } static void QD3D12_FlushQueuedBatches() { if (g_gl.queuedBatches.empty()) return; SetDynamicFixedFunctionState(g_gl.cmdList.Get()); QD3D12_BindTargetsForCurrentPhase(*g_currentWindow); const bool nativeColorOnly = (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE); if (!nativeColorOnly) g_gl.gbufferResolvedThisFrame = false; ID3D12PipelineState* lastPSO = nullptr; D3D12_GPU_DESCRIPTOR_HANDLE lastTex0{}; D3D12_GPU_DESCRIPTOR_HANDLE lastTex1{}; D3D12_GPU_DESCRIPTOR_HANDLE lastNormalMap{}; D3D12_GPU_DESCRIPTOR_HANDLE lastGlowMap{}; D3D12_GPU_DESCRIPTOR_HANDLE lastSpecularMap{}; D3D12_GPU_DESCRIPTOR_HANDLE lastNeuralWeights{}; D3D12_GPU_DESCRIPTOR_HANDLE lastNeuralLatent{}; bool haveLastTex0 = false; bool haveLastTex1 = false; bool haveLastNormalMap = false; bool haveLastGlowMap = false; bool haveLastSpecularMap = false; bool haveLastNeuralWeights = false; bool haveLastNeuralLatent = false; D3D12_PRIMITIVE_TOPOLOGY lastTopo = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; for (size_t i = 0; i < g_gl.queuedBatches.size(); ++i) { const QueuedBatch& batch = g_gl.queuedBatches[i]; const GLVertex* verts = QD3D12_GetBatchVertices(batch); const size_t count = batch.vertexCount; if (count <= 0) continue; if (batch.key.fogEnabled > 0.5f && batch.key.cameraValid > 0.5f && batch.key.depthTest && batch.key.depthWrite && batch.key.colorWriteMask != 0) { g_gl.sceneFogValidThisFrame = true; g_gl.sceneFogMode = batch.key.fogMode; g_gl.sceneFogDensity = batch.key.fogDensity; g_gl.sceneFogStart = batch.key.fogStart; g_gl.sceneFogEnd = batch.key.fogEnd; g_gl.sceneFogColor[0] = batch.key.fogColor[0]; g_gl.sceneFogColor[1] = batch.key.fogColor[1]; g_gl.sceneFogColor[2] = batch.key.fogColor[2]; g_gl.sceneFogColor[3] = batch.key.fogColor[3]; g_gl.sceneFogCameraWorldPos[0] = batch.key.cameraWorldPos[0]; g_gl.sceneFogCameraWorldPos[1] = batch.key.cameraWorldPos[1]; g_gl.sceneFogCameraWorldPos[2] = batch.key.cameraWorldPos[2]; g_gl.sceneFogCameraValid = batch.key.cameraValid; } const UINT vbBytes = (UINT)(count * sizeof(GLVertex)); const UINT cbBytes = (UINT)sizeof(DrawConstants); if (QD3D12_EnsureUploadSpaceForDraw(vbBytes, cbBytes)) { // The command list was closed/executed/reset, so all cached command-list // state is invalid. SetDynamicFixedFunctionState(g_gl.cmdList.Get()); QD3D12_BindTargetsForCurrentPhase(*g_currentWindow); lastPSO = nullptr; haveLastTex0 = false; haveLastTex1 = false; haveLastNormalMap = false; haveLastGlowMap = false; haveLastSpecularMap = false; haveLastNeuralWeights = false; haveLastNeuralLatent = false; lastTopo = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; } QD3D12_EmitQueryMarkers(batch.markerBegin, batch.markerEnd); g_gl.cmdList->RSSetViewports(1, &batch.key.viewport); g_gl.cmdList->RSSetScissorRects(1, &batch.key.scissor); UploadAlloc vbAlloc = QD3D12_AllocUpload(vbBytes, 256); memcpy(vbAlloc.cpu, verts, vbBytes); UploadAlloc cbAlloc = QD3D12_AllocUpload(cbBytes, 256); DrawConstants* dc = reinterpret_cast(cbAlloc.cpu); memset(dc, 0, sizeof(*dc)); dc->mvp = batch.key.mvp; dc->prevMvp = batch.key.prevMvp; dc->geometryFlag = batch.key.geometryFlag; dc->roughness = batch.key.roughness; dc->materialType = batch.key.materialType; dc->alphaFunc = batch.key.alphaFunc; dc->modelMatrix = batch.key.modelMatrix; dc->alphaRef = batch.key.alphaRef; dc->useTex0 = batch.key.useTex0; dc->useTex1 = batch.key.useTex1; dc->tex1IsLightmap = batch.key.tex1IsLightmap; dc->texEnvMode0 = batch.key.texEnvMode0; dc->texEnvMode1 = batch.key.texEnvMode1; memcpy(dc->texComb0RGB, batch.key.texComb0RGB, sizeof(dc->texComb0RGB)); memcpy(dc->texComb0Alpha, batch.key.texComb0Alpha, sizeof(dc->texComb0Alpha)); memcpy(dc->texComb0Operand, batch.key.texComb0Operand, sizeof(dc->texComb0Operand)); memcpy(dc->texEnvColor0, batch.key.texEnvColor0, sizeof(dc->texEnvColor0)); memcpy(dc->texComb1RGB, batch.key.texComb1RGB, sizeof(dc->texComb1RGB)); memcpy(dc->texComb1Alpha, batch.key.texComb1Alpha, sizeof(dc->texComb1Alpha)); memcpy(dc->texComb1Operand, batch.key.texComb1Operand, sizeof(dc->texComb1Operand)); memcpy(dc->texEnvColor1, batch.key.texEnvColor1, sizeof(dc->texEnvColor1)); dc->fogEnabled = batch.key.fogEnabled; dc->fogMode = batch.key.fogMode; dc->fogDensity = batch.key.fogDensity; dc->fogStart = batch.key.fogStart; dc->fogEnd = batch.key.fogEnd; dc->fogColor[0] = batch.key.fogColor[0]; dc->fogColor[1] = batch.key.fogColor[1]; dc->fogColor[2] = batch.key.fogColor[2]; dc->fogColor[3] = batch.key.fogColor[3]; const float activeWidth = (float)QD3D12_ActiveRasterWidth(*g_currentWindow); const float activeHeight = (float)QD3D12_ActiveRasterHeight(*g_currentWindow); dc->renderSize[0] = activeWidth; dc->renderSize[1] = activeHeight; dc->invRenderSize[0] = activeWidth > 0.0f ? (1.0f / activeWidth) : 0.0f; dc->invRenderSize[1] = activeHeight > 0.0f ? (1.0f / activeHeight) : 0.0f; dc->jitterPixels[0] = g_gl.jitterX; dc->jitterPixels[1] = g_gl.jitterY; dc->prevJitterPixels[0] = g_gl.prevJitterX; dc->prevJitterPixels[1] = g_gl.prevJitterY; 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; dc->materialMapPad[0] = batch.key.useSpecularMap; dc->materialMapPad[1] = batch.key.specularMapStrength; dc->materialMapPad[2] = QD3D12_PipelineUsesAlphaBlend(batch.key.pipeline) ? 1.0f : 0.0f; // Normal-map strength now drives a visibly stronger object-space displacement. // The hull shader can now subdivide much deeper, so the CPU side permits a // deeper push too while the shader filters/suppresses tiny bump-map noise. // Default strength 1.0 gives about +/-0.08 object units before shader-side // relief/distance gating. This keeps close relief visible but avoids the // large outward slab offsets that RGB-only pseudo-height can create. const float tessStrength = ClampValue(batch.key.normalMapStrength, 0.0f, 4.0f); const float tessDisplacementScale = 0.08f; dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ? ClampValue(tessStrength * tessDisplacementScale, 0.0f, 0.50f) : 0.0f; dc->cameraPomPad[0] = batch.key.cameraWorldPos[0]; dc->cameraPomPad[1] = batch.key.cameraWorldPos[1]; dc->cameraPomPad[2] = batch.key.cameraWorldPos[2]; dc->cameraPomPad[3] = batch.key.cameraValid; dc->neuralPomPad[0] = batch.key.useNeuralPOM; dc->neuralPomPad[1] = 0.0f; // Neural POM uses the same material strength knob as the normal/tessellation // path. Strength 1.0 is unchanged; values like 2.0-3.0 give visibly deeper // learned relief while the shader clamps extreme UV walks to avoid smearing. const float neuralStrength = ClampValue(batch.key.normalMapStrength, 0.0f, 4.0f); dc->neuralPomPad[2] = ClampValue(1.0f + (neuralStrength - 1.0f) * 0.65f, 1.0f, 3.0f); dc->neuralPomPad[3] = ClampValue(1.0f + (neuralStrength - 1.0f) * 0.45f, 1.0f, 2.5f); if (batch.key.useARBPrograms) { memcpy(dc->arbEnv, batch.key.arbConstants.env, sizeof(dc->arbEnv)); memcpy(dc->arbLocalVP, batch.key.arbConstants.vertexLocal, sizeof(dc->arbLocalVP)); memcpy(dc->arbLocalFP, batch.key.arbConstants.fragmentLocal, sizeof(dc->arbLocalFP)); } D3D12_VERTEX_BUFFER_VIEW vbv{}; vbv.BufferLocation = vbAlloc.gpu; vbv.SizeInBytes = (UINT)(count * sizeof(GLVertex)); vbv.StrideInBytes = sizeof(GLVertex); ID3D12PipelineState* pso = nullptr; const D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType = QD3D12_EffectiveTopologyTypeForPSO(batch.key, nativeColorOnly); if (GetTopologyTypeFromTopology(batch.key.topology) == D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT) { dc->PointSize = g_gl.pointSize; } else { dc->PointSize = 1.0f; } if (batch.key.useARBPrograms) { pso = QD3D12_GetARBPSO(batch.key, topoType, nativeColorOnly); if (!pso) continue; } else { pso = QD3D12_GetPSO(batch.key, topoType, nativeColorOnly); } if (pso != lastPSO) { g_gl.cmdList->SetPipelineState(pso); lastPSO = pso; } if (batch.key.useARBPrograms) { for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { UINT srvIndex = batch.key.textureSrvIndex[unit]; if (srvIndex == UINT_MAX) srvIndex = g_gl.whiteTexture.srvIndex; g_gl.cmdList->SetGraphicsRootDescriptorTable(1 + unit, QD3D12_SrvGpu(srvIndex)); } haveLastTex0 = false; haveLastTex1 = false; haveLastNormalMap = false; haveLastGlowMap = false; haveLastSpecularMap = false; haveLastNeuralWeights = false; haveLastNeuralLatent = false; } else { D3D12_GPU_DESCRIPTOR_HANDLE tex0Gpu = QD3D12_SrvGpu(batch.key.tex0SrvIndex); D3D12_GPU_DESCRIPTOR_HANDLE tex1Gpu = QD3D12_SrvGpu(batch.key.tex1SrvIndex); if (!haveLastTex0 || tex0Gpu.ptr != lastTex0.ptr) { // Don't know if this should be here or not could burry bugs, but rather then crash set to white. if (batch.key.tex0SrvIndex == UINT_MAX) { tex0Gpu = QD3D12_SrvGpu(g_gl.whiteTexture.srvIndex); } g_gl.cmdList->SetGraphicsRootDescriptorTable(1, tex0Gpu); lastTex0 = tex0Gpu; haveLastTex0 = true; } if (!haveLastTex1 || tex1Gpu.ptr != lastTex1.ptr) { g_gl.cmdList->SetGraphicsRootDescriptorTable(2, tex1Gpu); lastTex1 = tex1Gpu; haveLastTex1 = true; } UINT normalSrvIndex = batch.key.normalMapSrvIndex; if (normalSrvIndex == UINT_MAX) normalSrvIndex = g_gl.whiteTexture.srvIndex; D3D12_GPU_DESCRIPTOR_HANDLE normalMapGpu = QD3D12_SrvGpu(normalSrvIndex); if (!haveLastNormalMap || normalMapGpu.ptr != lastNormalMap.ptr) { g_gl.cmdList->SetGraphicsRootDescriptorTable(3, normalMapGpu); 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; } UINT specularSrvIndex = batch.key.specularMapSrvIndex; if (specularSrvIndex == UINT_MAX) specularSrvIndex = g_gl.whiteTexture.srvIndex; D3D12_GPU_DESCRIPTOR_HANDLE specularMapGpu = QD3D12_SrvGpu(specularSrvIndex); if (!haveLastSpecularMap || specularMapGpu.ptr != lastSpecularMap.ptr) { g_gl.cmdList->SetGraphicsRootDescriptorTable(5, specularMapGpu); lastSpecularMap = specularMapGpu; haveLastSpecularMap = true; } UINT neuralWeightsSrvIndex = batch.key.neuralPOMWeightsSrvIndex; UINT neuralLatentSrvIndex = batch.key.neuralPOMLatentSrvIndex; if (neuralWeightsSrvIndex == UINT_MAX) neuralWeightsSrvIndex = QD3D12_NeuralPOMFallbackSrvIndex(); if (neuralLatentSrvIndex == UINT_MAX) neuralLatentSrvIndex = QD3D12_NeuralPOMFallbackSrvIndex(); D3D12_GPU_DESCRIPTOR_HANDLE neuralWeightsGpu = QD3D12_SrvGpu(neuralWeightsSrvIndex); D3D12_GPU_DESCRIPTOR_HANDLE neuralLatentGpu = QD3D12_SrvGpu(neuralLatentSrvIndex); if (!haveLastNeuralWeights || neuralWeightsGpu.ptr != lastNeuralWeights.ptr) { g_gl.cmdList->SetGraphicsRootDescriptorTable(6, neuralWeightsGpu); lastNeuralWeights = neuralWeightsGpu; haveLastNeuralWeights = true; } if (!haveLastNeuralLatent || neuralLatentGpu.ptr != lastNeuralLatent.ptr) { g_gl.cmdList->SetGraphicsRootDescriptorTable(7, neuralLatentGpu); lastNeuralLatent = neuralLatentGpu; haveLastNeuralLatent = true; } } g_gl.cmdList->SetGraphicsRootConstantBufferView(0, cbAlloc.gpu); g_gl.cmdList->OMSetStencilRef(batch.key.stencilRef); const D3D12_PRIMITIVE_TOPOLOGY drawTopology = QD3D12_EffectiveIATopologyForDraw(batch.key, nativeColorOnly); if (drawTopology != lastTopo) { g_gl.cmdList->IASetPrimitiveTopology(drawTopology); lastTopo = drawTopology; } g_gl.cmdList->IASetVertexBuffers(0, 1, &vbv); g_gl.cmdList->DrawInstanced((UINT)count, 1, 0, 0); } g_gl.queuedBatches.clear(); } // ============================================================ // SECTION 11: GL exports // ============================================================ const GLubyte* APIENTRY glGetString(GLenum name) { switch (name) { case GL_VENDOR: return (const GLubyte*)vendor; case GL_RENDERER: return (const GLubyte*)renderer; case GL_VERSION: return (const GLubyte*)version; case GL_EXTENSIONS: return (const GLubyte*)extensions; case GL_PROGRAM_ERROR_STRING_ARB: return (const GLubyte*)QD3D12ARB_GetProgramErrorString(); default: return (const GLubyte*)""; } } void APIENTRY glClearColor(GLclampf r, GLclampf g, GLclampf b, GLclampf a) { g_gl.clearColor[0] = r; g_gl.clearColor[1] = g; g_gl.clearColor[2] = b; g_gl.clearColor[3] = a; } static D3D12_RECT QD3D12_GetActiveClearRect() { const UINT activeWidth = QD3D12_ActiveRasterWidth(*g_currentWindow); const UINT activeHeight = QD3D12_ActiveRasterHeight(*g_currentWindow); if (g_gl.scissorTest) { const float scaleX = (g_currentWindow->width > 0) ? ((float)activeWidth / (float)g_currentWindow->width) : 1.0f; const float scaleY = (g_currentWindow->height > 0) ? ((float)activeHeight / (float)g_currentWindow->height) : 1.0f; const LONG leftGL = (LONG)std::lround((double)g_gl.scissorX * scaleX); const LONG rightGL = (LONG)std::lround((double)(g_gl.scissorX + g_gl.scissorW) * scaleX); const LONG topGL = (LONG)std::lround((double)(g_gl.scissorY + g_gl.scissorH) * scaleY); const LONG bottomGL = (LONG)std::lround((double)g_gl.scissorY * scaleY); D3D12_RECT r{}; r.left = ClampValue(leftGL, 0, (LONG)activeWidth); r.right = ClampValue(rightGL, 0, (LONG)activeWidth); // OpenGL scissor is bottom-left origin, D3D12 is top-left origin. r.top = ClampValue((LONG)activeHeight - topGL, 0, (LONG)activeHeight); r.bottom = ClampValue((LONG)activeHeight - bottomGL, 0, (LONG)activeHeight); if (r.right < r.left) std::swap(r.right, r.left); if (r.bottom < r.top) std::swap(r.bottom, r.top); return r; } D3D12_RECT r{}; r.left = 0; r.top = 0; r.right = (LONG)activeWidth; r.bottom = (LONG)activeHeight; return r; } static void QD3D12_EnsureFrameOpen() { if (g_gl.frameOpen) { if (g_gl.frameOwner == g_currentWindow) return; QD3D12_SubmitOpenFrameNoPresentAndWait(); } QD3D12_BeginFrame(); } void APIENTRY glClear(GLbitfield mask) { if (mask == 0) return; QD3D12_EnsureFrameOpen(); QD3D12_FlushQueuedBatches(); QD3D12_BindTargetsForCurrentPhase(*g_currentWindow); const D3D12_RECT clearRect = QD3D12_GetActiveClearRect(); g_gl.cmdList->RSSetScissorRects(1, &clearRect); const bool nativePhase = (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE); if (!nativePhase) g_gl.gbufferResolvedThisFrame = false; if (mask & GL_COLOR_BUFFER_BIT) { const float cc[4] = { g_gl.clearColor[0], g_gl.clearColor[1], g_gl.clearColor[2], g_gl.clearColor[3] }; if (nativePhase) { g_gl.cmdList->ClearRenderTargetView(CurrentBackBufferRTV(), cc, 1, &clearRect); } else { // Keep the auxiliary MRT clears fixed to their creation-time values so the // debug layer does not spam CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE. // Material metadata is written by draws, not by glClear. 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 }; const float specularClear[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); g_gl.cmdList->ClearRenderTargetView(CurrentSpecularRTV(), specularClear, 1, &clearRect); } } D3D12_CPU_DESCRIPTOR_HANDLE dsv = nativePhase ? CurrentNativeDepthDSV() : CurrentActiveSceneDepthDSV(); if (mask & GL_DEPTH_BUFFER_BIT) { g_gl.cmdList->ClearDepthStencilView( dsv, D3D12_CLEAR_FLAG_DEPTH, (FLOAT)ClampValue(g_gl.clearDepthValue, 0.0, 1.0), 0, 1, &clearRect); } if (mask & GL_STENCIL_BUFFER_BIT) { g_gl.cmdList->ClearDepthStencilView( dsv, D3D12_CLEAR_FLAG_STENCIL, (FLOAT)ClampValue(g_gl.clearDepthValue, 0.0, 1.0), (UINT8)(g_gl.clearStencilValue & 0xFF), 1, &clearRect); } } void APIENTRY glViewport(GLint x, GLint y, GLsizei width, GLsizei height) { g_gl.viewportX = x; g_gl.viewportY = y; g_gl.viewportW = width; g_gl.viewportH = height; QD3D12_UpdateViewportState(); } void APIENTRY glEnable(GLenum cap) { switch (cap) { case GL_BLEND: g_gl.blend = true; break; case GL_ALPHA_TEST: g_gl.alphaTest = true; break; case GL_DEPTH_TEST: g_gl.depthTest = true; break; case GL_CULL_FACE: g_gl.cullFace = true; break; case GL_SCISSOR_TEST: g_gl.scissorTest = true; break; case GL_STENCIL_TEST: g_gl.stencilTest = true; break; #ifdef GL_STENCIL_TEST_TWO_SIDE_EXT case GL_STENCIL_TEST_TWO_SIDE_EXT: g_gl.stencilTwoSide = true; g_gl.stencilTest = true; break; #endif #ifdef GL_DEPTH_BOUNDS_TEST_EXT case GL_DEPTH_BOUNDS_TEST_EXT: g_gl.depthBoundsTest = true; break; #endif case GL_POLYGON_OFFSET_POINT: g_gl.polygonOffsetPoint = true; break; case GL_POLYGON_OFFSET_LINE: g_gl.polygonOffsetLine = true; break; case GL_POLYGON_OFFSET_FILL: g_gl.polygonOffsetFill = true; break; case GL_VERTEX_PROGRAM_ARB: case GL_FRAGMENT_PROGRAM_ARB: QD3D12ARB_SetEnabled(cap, true); break; case GL_QD3D12_TAA: QD3D12_EnableTAA(1); break; case GL_QD3D12_NEURAL_POM_ENABLED: glEnableNeuralPOMQD3D12(GL_TRUE); break; case GL_TEXTURE_2D: #ifdef GL_TEXTURE_RECTANGLE_ARB case GL_TEXTURE_RECTANGLE_ARB: #endif #ifdef GL_TEXTURE_CUBE_MAP_EXT case GL_TEXTURE_CUBE_MAP_EXT: #endif g_gl.texture2D[g_gl.activeTextureUnit] = true; break; case GL_FOG: g_gl.fog = true; break; default: break; } } void APIENTRY glDisable(GLenum cap) { switch (cap) { case GL_BLEND: g_gl.blend = false; break; case GL_ALPHA_TEST: g_gl.alphaTest = false; break; case GL_DEPTH_TEST: g_gl.depthTest = false; break; case GL_CULL_FACE: g_gl.cullFace = false; break; case GL_SCISSOR_TEST: g_gl.scissorTest = false; break; case GL_STENCIL_TEST: g_gl.stencilTest = false; break; #ifdef GL_STENCIL_TEST_TWO_SIDE_EXT case GL_STENCIL_TEST_TWO_SIDE_EXT: g_gl.stencilTwoSide = false; break; #endif #ifdef GL_DEPTH_BOUNDS_TEST_EXT case GL_DEPTH_BOUNDS_TEST_EXT: g_gl.depthBoundsTest = false; break; #endif case GL_POLYGON_OFFSET_POINT: g_gl.polygonOffsetPoint = false; break; case GL_POLYGON_OFFSET_LINE: g_gl.polygonOffsetLine = false; break; case GL_POLYGON_OFFSET_FILL: g_gl.polygonOffsetFill = false; break; case GL_VERTEX_PROGRAM_ARB: case GL_FRAGMENT_PROGRAM_ARB: QD3D12ARB_SetEnabled(cap, false); break; case GL_QD3D12_TAA: QD3D12_EnableTAA(0); break; case GL_QD3D12_NEURAL_POM_ENABLED: glEnableNeuralPOMQD3D12(GL_FALSE); break; case GL_TEXTURE_2D: #ifdef GL_TEXTURE_RECTANGLE_ARB case GL_TEXTURE_RECTANGLE_ARB: #endif #ifdef GL_TEXTURE_CUBE_MAP_EXT case GL_TEXTURE_CUBE_MAP_EXT: #endif g_gl.texture2D[g_gl.activeTextureUnit] = false; break; case GL_FOG: g_gl.fog = false; break; default: break; } } void APIENTRY glBlendFunc(GLenum sfactor, GLenum dfactor) { g_gl.blendSrc = sfactor; g_gl.blendDst = dfactor; } void APIENTRY glAlphaFunc(GLenum func, GLclampf ref) { g_gl.alphaFunc = func; g_gl.alphaRef = ref; } void APIENTRY glDepthMask(GLboolean flag) { g_gl.depthWrite = (flag != 0); } void APIENTRY glDepthRange(GLclampd zNear, GLclampd zFar) { g_gl.depthRangeNear = ClampValue(zNear, 0.0, 1.0); g_gl.depthRangeFar = ClampValue(zFar, 0.0, 1.0); QD3D12_UpdateViewportState(); } void APIENTRY glCullFace(GLenum mode) { g_gl.cullMode = mode; } void APIENTRY glPolygonMode(GLenum, GLenum) { } void APIENTRY glShadeModel(GLenum mode) { g_gl.shadeModel = mode; } void APIENTRY glHint(GLenum target, GLenum mode) { if (target == GL_FOG_HINT) g_gl.fogHint = mode; } void APIENTRY glFinish(void) { if (!g_gl.device || !g_gl.queue || !g_gl.cmdList) return; if (!g_gl.frameOpen && g_gl.queuedBatches.empty()) { QD3D12_WaitForGPU(); return; } QD3D12_EnsureFrameOpen(); QD3D12_FlushQueuedBatches(); QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } FrameResources& fr = g_currentWindow->frames[g_currentWindow->frameIndex]; fr.fenceValue = signalValue; QD3D12_CHECK(fr.cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(fr.cmdAlloc.Get(), nullptr)); QD3D12_BindTargetsForCurrentPhase(*g_currentWindow); g_gl.frameOpen = true; g_gl.frameOwner = g_currentWindow; } void APIENTRY glMatrixMode(GLenum mode) { g_gl.matrixMode = mode; } void APIENTRY glLoadIdentity(void) { QD3D12_CurrentMatrixStack().back() = Mat4::Identity(); } void APIENTRY glPushMatrix(void) { auto& s = QD3D12_CurrentMatrixStack(); s.push_back(s.back()); } void APIENTRY glPopMatrix(void) { auto& s = QD3D12_CurrentMatrixStack(); if (s.size() > 1) s.pop_back(); } void APIENTRY glTranslatef(GLfloat x, GLfloat y, GLfloat z) { auto& t = QD3D12_CurrentMatrixStack().back(); t = Mat4::Multiply(t, Mat4::Translation(x, y, z)); } void APIENTRY glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { auto& t = QD3D12_CurrentMatrixStack().back(); t = Mat4::Multiply(t, Mat4::RotationAxisDeg(angle, x, y, z)); } void APIENTRY glScalef(GLfloat x, GLfloat y, GLfloat z) { auto& t = QD3D12_CurrentMatrixStack().back(); t = Mat4::Multiply(t, Mat4::Scale(x, y, z)); } void APIENTRY glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) { auto& t = QD3D12_CurrentMatrixStack().back(); t = Mat4::Multiply(t, Mat4::Ortho(left, right, bottom, top, zNear, zFar)); } void APIENTRY glBegin(GLenum mode) { assert(!g_gl.inBeginEnd); g_gl.inBeginEnd = true; g_gl.currentPrim = mode; g_gl.immediateVerts.Clear(); } void APIENTRY glEnd(void) { assert(g_gl.inBeginEnd); g_gl.inBeginEnd = false; FlushImmediate(g_gl.currentPrim, g_gl.immediateVerts.Data(), g_gl.immediateVerts.Size()); } void APIENTRY glVertex2f(GLfloat x, GLfloat y) { glVertex3f(x, y, 0.0f); } void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { GLVertex& v = g_gl.immediateVerts.Push(); v.px = x; v.py = y; v.pz = z; v.nx = g_gl.curNormal[0]; v.ny = g_gl.curNormal[1]; v.nz = g_gl.curNormal[2]; v.tx = g_gl.curTangent[0]; v.ty = g_gl.curTangent[1]; v.tz = g_gl.curTangent[2]; v.bx = g_gl.curBinormal[0]; v.by = g_gl.curBinormal[1]; v.bz = g_gl.curBinormal[2]; v.u0 = g_gl.curU[0]; v.v0 = g_gl.curV[0]; v.u1 = g_gl.curU[1]; v.v1 = g_gl.curV[1]; v.r = g_gl.curColor[0]; v.g = g_gl.curColor[1]; v.b = g_gl.curColor[2]; v.a = g_gl.curColor[3]; } void APIENTRY glVertex3fv(const GLfloat* v) { glVertex3f(v[0], v[1], v[2]); } void APIENTRY glTexCoord2f(GLfloat s, GLfloat t) { g_gl.curU[g_gl.activeTextureUnit] = s; g_gl.curV[g_gl.activeTextureUnit] = t; } void APIENTRY glColor3f(GLfloat r, GLfloat g, GLfloat b) { g_gl.curColor[0] = r; g_gl.curColor[1] = g; g_gl.curColor[2] = b; g_gl.curColor[3] = 1.0f; } void APIENTRY glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { g_gl.curColor[0] = r; g_gl.curColor[1] = g; g_gl.curColor[2] = b; g_gl.curColor[3] = a; } void APIENTRY glGenTextures(GLsizei n, GLuint* textures) { for (GLsizei i = 0; i < n; ++i) { GLuint id = g_gl.nextTextureId++; TextureResource tex{}; tex.glId = id; tex.srvIndex = UINT_MAX; tex.minFilter = g_gl.defaultMinFilter; tex.magFilter = g_gl.defaultMagFilter; tex.wrapS = g_gl.defaultWrapS; tex.wrapT = g_gl.defaultWrapT; g_gl.textures[id] = tex; textures[i] = id; } } void APIENTRY glDeleteTextures(GLsizei n, const GLuint* textures) { for (GLsizei i = 0; i < n; ++i) { const GLuint deadId = textures[i]; for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit) { if (g_gl.boundTexture[unit] == deadId) g_gl.boundTexture[unit] = 0; } if (g_gl.currentNormalMapTexture == deadId) g_gl.currentNormalMapTexture = 0; if (g_gl.currentGlowMapTexture == deadId) g_gl.currentGlowMapTexture = 0; if (g_gl.currentSpecularMapTexture == deadId) g_gl.currentSpecularMapTexture = 0; if (g_gl.currentNeuralPOMTexture == deadId) g_gl.currentNeuralPOMTexture = 0; auto it = g_gl.textures.find(deadId); if (it != g_gl.textures.end()) { QD3D12_RetireResource(it->second.texture); QD3D12_RetireResource(it->second.neuralPOM.weightsBuffer); QD3D12_RetireResource(it->second.neuralPOM.latentBuffer); it->second.gpuValid = false; g_gl.textures.erase(it); } } } #ifdef _DEBUG #pragma optimize off #endif void APIENTRY glBindTexture(GLenum, GLuint texture) { g_gl.boundTexture[g_gl.activeTextureUnit] = texture; if (texture == 0) return; (void)QD3D12_EnsureTextureName(texture); } void APIENTRY glTagTextureNormalMap(GLuint texture, GLboolean isNormalMap) { if (texture == 0) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource& tex = QD3D12_EnsureTextureName(texture); tex.isNormalMap = (isNormalMap != GL_FALSE); } void APIENTRY glTextureNormalMap(GLuint texture, GLboolean isNormalMap) { glTagTextureNormalMap(texture, isNormalMap); } void APIENTRY glBindNormalMapTexture(GLuint texture) { if (texture != 0) (void)QD3D12_EnsureTextureName(texture); g_gl.currentNormalMapTexture = texture; } void APIENTRY glNormalMapTexture(GLuint texture) { glBindNormalMapTexture(texture); } void APIENTRY glNormalMapStrengthf(GLfloat strength) { g_gl.currentNormalMapStrength = (strength < 0.0f) ? 0.0f : strength; } void APIENTRY glNormalMapYSignf(GLfloat sign) { g_gl.currentNormalMapYSign = (sign < 0.0f) ? -1.0f : 1.0f; } static uint16_t QD3D12_FloatToHalfBits(float f) { union { float f; uint32_t u; } v; v.f = f; uint32_t sign = (v.u >> 16) & 0x8000u; int exp = (int)((v.u >> 23) & 0xFFu) - 127 + 15; uint32_t mant = v.u & 0x7FFFFFu; if (exp <= 0) { if (exp < -10) return (uint16_t)sign; mant = (mant | 0x800000u) >> (1 - exp); return (uint16_t)(sign | ((mant + 0x1000u) >> 13)); } if (exp >= 31) return (uint16_t)(sign | 0x7C00u); return (uint16_t)(sign | ((uint32_t)exp << 10) | ((mant + 0x1000u) >> 13)); } static bool QD3D12_TryBuildNeuralPOMLatentRGBA16F( const GLvoid* weightsData, GLsizei weightsBytes, const GLvoid* latentData, GLsizei latentBytes, std::vector& outLatentRGBA16F) { outLatentRGBA16F.clear(); if (!weightsData || weightsBytes < 32 || !latentData || latentBytes <= 0) return false; const uint8_t* wb = reinterpret_cast(weightsData); auto ReadU32LE = [](const uint8_t* p) -> uint32_t { return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24); }; const uint32_t magic = ReadU32LE(wb + 0); const uint32_t version = ReadU32LE(wb + 4); if (magic != 0x4D504E49u || version != 1u) return false; const uint32_t latentRes = ReadU32LE(wb + 20); const uint32_t latentChannels = ReadU32LE(wb + 24); if (latentRes == 0u || latentRes > 4096u || latentChannels == 0u || latentChannels > 16u) return false; const uint64_t texels = uint64_t(latentRes) * uint64_t(latentRes); const uint64_t rgbaSlices = (uint64_t(latentChannels) + 3ull) / 4ull; const uint64_t expectedHalfBytes = texels * rgbaSlices * 4ull * sizeof(uint16_t); const uint64_t expectedF32Bytes = texels * uint64_t(latentChannels) * sizeof(float); if (expectedHalfBytes > SIZE_MAX || expectedF32Bytes > SIZE_MAX) return false; // Correct runtime format: latent RGBA16F slices exactly as written by the trainer. if ((uint64_t)latentBytes == expectedHalfBytes) { outLatentRGBA16F.resize((size_t)expectedHalfBytes); memcpy(outLatentRGBA16F.data(), latentData, (size_t)expectedHalfBytes); return true; } // Friendly compatibility path: if the caller accidentally passes _latent_f32.bin, // convert it here instead of letting the shader read FP32 bits as packed halves. if ((uint64_t)latentBytes == expectedF32Bytes) { outLatentRGBA16F.assign((size_t)expectedHalfBytes, 0u); const float* src = reinterpret_cast(latentData); uint16_t* dst = reinterpret_cast(outLatentRGBA16F.data()); for (uint32_t y = 0; y < latentRes; ++y) { for (uint32_t x = 0; x < latentRes; ++x) { const uint64_t srcBase = (uint64_t(y) * latentRes + x) * uint64_t(latentChannels); const uint64_t dstTexel = uint64_t(y) * latentRes + x; for (uint32_t c = 0; c < latentChannels; ++c) { const uint32_t slice = c >> 2u; const uint32_t comp = c & 3u; const uint64_t dstHalf = ((uint64_t(slice) * texels + dstTexel) * 4ull + comp); dst[dstHalf] = QD3D12_FloatToHalfBits(src[srcBase + c]); } } } QD3D12_Log("NeuralPOM: converted latent_f32 input to runtime latent_rgba16f format."); return true; } QD3D12_Log( "NeuralPOM: latent byte size %d does not match expected RGBA16F %llu or F32 %llu; using supplied bytes anyway.", (int)latentBytes, (unsigned long long)expectedHalfBytes, (unsigned long long)expectedF32Bytes); outLatentRGBA16F.resize((size_t)latentBytes); memcpy(outLatentRGBA16F.data(), latentData, (size_t)latentBytes); return true; } static void QD3D12_ClearNeuralPOMResource(TextureResource& tex) { NeuralPOMResource& npom = tex.neuralPOM; npom.hasData = false; npom.gpuValid = false; ++npom.generation; npom.weightsBytes.clear(); npom.latentRGBA16FBytes.clear(); QD3D12_RetireResource(npom.weightsBuffer); QD3D12_RetireResource(npom.latentBuffer); npom.weightsState = D3D12_RESOURCE_STATE_COMMON; npom.latentState = D3D12_RESOURCE_STATE_COMMON; } void APIENTRY glNeuralPOMMaterialQD3D12(GLuint texture, GLsizei weightsBytes, const GLvoid* weightsData, GLsizei latentBytes, const GLvoid* latentRGBA16FData) { if (texture == 0 || weightsBytes <= 0 || latentBytes <= 0 || !weightsData || !latentRGBA16FData) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource& tex = QD3D12_EnsureTextureName(texture); // Do not force this texture into the normal-map role here. The caller may attach // the Neural POM payload to the diffuse texture or to a separate normal-map // texture. Marking a diffuse texture as isNormalMap makes the fixed-function // color path treat it as material-map-only, which can remove the base texture // from the draw entirely. NeuralPOMResource& npom = tex.neuralPOM; npom.weightsBytes.resize((size_t)weightsBytes); memcpy(npom.weightsBytes.data(), weightsData, (size_t)weightsBytes); if (!QD3D12_TryBuildNeuralPOMLatentRGBA16F( weightsData, weightsBytes, latentRGBA16FData, latentBytes, npom.latentRGBA16FBytes)) { npom.latentRGBA16FBytes.resize((size_t)latentBytes); memcpy(npom.latentRGBA16FBytes.data(), latentRGBA16FData, (size_t)latentBytes); } npom.hasData = true; npom.gpuValid = false; ++npom.generation; if (npom.generation == 0) ++npom.generation; QD3D12_RetireResource(npom.weightsBuffer); QD3D12_RetireResource(npom.latentBuffer); npom.weightsState = D3D12_RESOURCE_STATE_COMMON; npom.latentState = D3D12_RESOURCE_STATE_COMMON; } void APIENTRY glNeuralPOMMaterialFromBoundTextureQD3D12(GLsizei weightsBytes, const GLvoid* weightsData, GLsizei latentBytes, const GLvoid* latentRGBA16FData) { GLuint texture = g_gl.boundTexture[g_gl.activeTextureUnit]; glNeuralPOMMaterialQD3D12(texture, weightsBytes, weightsData, latentBytes, latentRGBA16FData); } void APIENTRY glClearNeuralPOMMaterialQD3D12(GLuint texture) { if (texture == 0) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource* tex = QD3D12_FindTextureResource(texture); if (!tex) return; QD3D12_ClearNeuralPOMResource(*tex); if (g_gl.currentNeuralPOMTexture == texture) g_gl.currentNeuralPOMTexture = 0; } void APIENTRY glBindNeuralPOMTextureQD3D12(GLuint texture) { if (texture != 0) (void)QD3D12_EnsureTextureName(texture); g_gl.currentNeuralPOMTexture = texture; } void APIENTRY glNeuralPOMTextureQD3D12(GLuint texture) { glBindNeuralPOMTextureQD3D12(texture); } void APIENTRY glEnableNeuralPOMQD3D12(GLboolean enable) { g_gl.neuralPOMEnabled = (enable != GL_FALSE); } 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 glTagTextureSpecularMap(GLuint texture, GLboolean isSpecularMap) { if (texture == 0) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource& tex = QD3D12_EnsureTextureName(texture); tex.isSpecularMap = (isSpecularMap != GL_FALSE); } void APIENTRY glTextureSpecularMap(GLuint texture, GLboolean isSpecularMap) { glTagTextureSpecularMap(texture, isSpecularMap); } void APIENTRY glBindSpecularMapTexture(GLuint texture) { if (texture != 0) { TextureResource& tex = QD3D12_EnsureTextureName(texture); if ((tex.isNormalMap && !tex.isSpecularMap) || (tex.isGlowMap && !tex.isSpecularMap)) { QD3D12_Log("glBindSpecularMapTexture(%u) ignored: texture is tagged as a different material map role.", texture); g_gl.currentSpecularMapTexture = 0; return; } } g_gl.currentSpecularMapTexture = texture; } void APIENTRY glSpecularMapTexture(GLuint texture) { glBindSpecularMapTexture(texture); } void APIENTRY glSpecularMapStrengthf(GLfloat strength) { g_gl.currentSpecularMapStrength = (strength < 0.0f) ? 0.0f : strength; } void APIENTRY glRaytracingMaterialFlagsQD3D12(GLuint flags) { g_gl.currentRayMaterialFlags = QD3D12_ClampRayMaterialFlags((uint32_t)flags); } void APIENTRY glRaytracingMaterialFlagQD3D12(GLuint flag, GLboolean enable) { const uint32_t bit = QD3D12_ClampRayMaterialFlags((uint32_t)flag); if (enable != GL_FALSE) g_gl.currentRayMaterialFlags = QD3D12_ClampRayMaterialFlags(g_gl.currentRayMaterialFlags | bit); else g_gl.currentRayMaterialFlags = QD3D12_ClampRayMaterialFlags(g_gl.currentRayMaterialFlags & ~bit); } void APIENTRY glGlassMaterialQD3D12(GLboolean enable) { glRaytracingMaterialFlagQD3D12(GL_RAYTRACING_MATERIAL_FLAG_GLASS, enable); } void APIENTRY glMaterialGlassQD3D12(GLboolean enable) { glGlassMaterialQD3D12(enable); } #ifdef _DEBUG #pragma optimize on #endif void APIENTRY glLoadMatrixf(const GLfloat* m) { if (!m) return; auto& top = QD3D12_CurrentMatrixStack().back(); memcpy(top.m, m, sizeof(top.m)); } void APIENTRY glGetIntegerv(GLenum pname, GLint* params) { if (!params) return; switch (pname) { case GL_MAX_TEXTURES_SGIS: case GL_MAX_ACTIVE_TEXTURES_ARB: case GL_MAX_TEXTURE_IMAGE_UNITS_ARB: case GL_MAX_TEXTURE_COORDS_ARB: *params = (GLint)QD3D12_MaxTextureUnits; break; case GL_MAX_PROGRAM_ENV_PARAMETERS_ARB: case GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB: case GL_MAX_PROGRAM_PARAMETERS_ARB: case GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB: *params = (GLint)QD3D12_ARB_MAX_PROGRAM_PARAMETERS; break; case GL_MAX_VERTEX_ATTRIBS_ARB: case GL_MAX_PROGRAM_ATTRIBS_ARB: case GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB: *params = (GLint)QD3D12_ARB_MAX_VERTEX_ATTRIBS; break; case GL_MAX_PROGRAM_INSTRUCTIONS_ARB: case GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB: *params = 1024; break; case GL_MAX_PROGRAM_TEMPORARIES_ARB: case GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB: *params = 64; break; case GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB: case GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB: *params = 1; break; case GL_MAX_PROGRAM_MATRICES_ARB: *params = 8; break; case GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB: *params = 4; break; case GL_VERTEX_PROGRAM_BINDING_ARB: *params = (GLint)QD3D12ARB_GetBoundVertexProgram(); break; case GL_FRAGMENT_PROGRAM_BINDING_ARB: *params = (GLint)QD3D12ARB_GetBoundFragmentProgram(); break; case GL_PROGRAM_ERROR_POSITION_ARB: *params = QD3D12ARB_GetProgramErrorPosition(); break; case GL_VIEWPORT: params[0] = g_gl.viewportX; params[1] = g_gl.viewportY; params[2] = (GLint)g_gl.viewportW; params[3] = (GLint)g_gl.viewportH; break; case GL_DRAW_BUFFER: *params = (GLint)g_gl.drawBuffer; break; case GL_READ_BUFFER: *params = (GLint)g_gl.readBuffer; break; case GL_FOG_MODE: *params = (GLint)g_gl.fogMode; break; case GL_FOG_HINT: *params = (GLint)g_gl.fogHint; break; case GL_SELECTED_TEXTURE_SGIS: *params = (GLint)(GL_TEXTURE0_SGIS + g_gl.activeTextureUnit); break; case GL_ACTIVE_TEXTURE_ARB: *params = (GLint)(GL_TEXTURE0_ARB + g_gl.activeTextureUnit); break; case GL_CLIENT_ACTIVE_TEXTURE_ARB: *params = (GLint)(GL_TEXTURE0_ARB + g_gl.clientActiveTextureUnit); break; case GL_NORMAL_MAP_BINDING_QD3D12: *params = (GLint)g_gl.currentNormalMapTexture; break; case GL_GLOW_MAP_BINDING_QD3D12: *params = (GLint)g_gl.currentGlowMapTexture; break; case GL_SPECULAR_MAP_BINDING_QD3D12: *params = (GLint)g_gl.currentSpecularMapTexture; break; case GL_QD3D12_NEURAL_POM_BINDING: *params = (GLint)g_gl.currentNeuralPOMTexture; break; case GL_QD3D12_NEURAL_POM_ENABLED: *params = g_gl.neuralPOMEnabled ? 1 : 0; break; case GL_QD3D12_MATERIAL_FLAGS: *params = (GLint)QD3D12_CurrentRayMaterialFlags(); break; case GL_QD3D12_UPSCALER_BACKEND: *params = (GLint)g_gl.upscalerBackend; break; case GL_QD3D12_UPSCALER_QUALITY: *params = (GLint)g_gl.upscalerQuality; break; case GL_QD3D12_DLAA_ENABLED: *params = (GLint)QD3D12_IsDLAAEnabled(); break; case GL_QD3D12_TAA_ENABLED: *params = (GLint)QD3D12_IsTAAEnabled(); break; #ifdef GL_ACTIVE_STENCIL_FACE_EXT case GL_ACTIVE_STENCIL_FACE_EXT: *params = (GLint)g_gl.activeStencilFace; break; #endif case GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB: *params = 4; break; case GL_COMPRESSED_TEXTURE_FORMATS_ARB: params[0] = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; params[1] = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; params[2] = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; params[3] = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; case GL_MAX_TEXTURE_SIZE: *params = 4096; break; case GL_CURRENT_COLOR: params[0] = (GLint)g_gl.curColor[0]; params[1] = (GLint)g_gl.curColor[1]; params[2] = (GLint)g_gl.curColor[2]; params[3] = (GLint)g_gl.curColor[3]; break; default: *params = 0; break; } } void APIENTRY glGetFloatv(GLenum pname, GLfloat* params) { if (!params) return; switch (pname) { case GL_FOG_DENSITY: params[0] = g_gl.fogDensity; break; case GL_FOG_START: params[0] = g_gl.fogStart; break; case GL_FOG_END: params[0] = g_gl.fogEnd; break; case GL_FOG_COLOR: params[0] = g_gl.fogColor[0]; params[1] = g_gl.fogColor[1]; params[2] = g_gl.fogColor[2]; params[3] = g_gl.fogColor[3]; break; #ifdef GL_DEPTH_BOUNDS_EXT case GL_DEPTH_BOUNDS_EXT: params[0] = (GLfloat)g_gl.depthBoundsMin; params[1] = (GLfloat)g_gl.depthBoundsMax; break; #endif case GL_QD3D12_TONEMAP_BRIGHTNESS: params[0] = g_gl.toneMapBrightness; break; case GL_MODELVIEW_MATRIX: memcpy(params, g_gl.modelStack.back().m, sizeof(GLfloat) * 16); break; case GL_PROJECTION_MATRIX: memcpy(params, g_gl.projStack.back().m, sizeof(GLfloat) * 16); break; case GL_CURRENT_COLOR: params[0] = g_gl.curColor[0]; params[1] = g_gl.curColor[1]; params[2] = g_gl.curColor[2]; params[3] = g_gl.curColor[3]; break; case GL_POLYGON_OFFSET_FACTOR: params[0] = g_gl.polygonOffsetFactor; break; case GL_POLYGON_OFFSET_UNITS: params[0] = g_gl.polygonOffsetUnits; break; default: memset(params, 0, sizeof(GLfloat) * 16); break; } } void APIENTRY glGetDoublev(GLenum pname, GLdouble* params) { if (!params) return; switch (pname) { case GL_MODELVIEW_MATRIX: for (int i = 0; i < 16; ++i) params[i] = (GLdouble)g_gl.modelStack.back().m[i]; break; case GL_PROJECTION_MATRIX: for (int i = 0; i < 16; ++i) params[i] = (GLdouble)g_gl.projStack.back().m[i]; break; case GL_CURRENT_COLOR: params[0] = (GLdouble)g_gl.curColor[0]; params[1] = (GLdouble)g_gl.curColor[1]; params[2] = (GLdouble)g_gl.curColor[2]; params[3] = (GLdouble)g_gl.curColor[3]; break; default: for (int i = 0; i < 16; ++i) params[i] = 0.0; break; } } void APIENTRY glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) { auto Quantize = [](float v, float steps) -> float { return floorf(v * steps + 0.5f) / steps; }; const float l = (float)left; const float r = (float)right; const float b = (float)bottom; const float t = (float)top; const float n = (float)zNear; const float fz = (float)zFar; const float invW = 1.0f / (r - l); const float invH = 1.0f / (t - b); const float invD = 1.0f / (fz - n); float xScale = (2.0f * n) * invW; float yScale = (2.0f * n) * invH; float xCenter = (r + l) * invW; float yCenter = (t + b) * invH; float zScale = -(fz + n) * invD; float zTrans = -(2.0f * fz * n) * invD; // // Intentionally degrade precision a bit to feel less "perfect OpenGL". // Tune these to taste. // // Lower numbers = chunkier / more old-school distortion feel. // const float scaleQuant = 256.0f; const float centerQuant = 128.0f; const float depthQuant = 1024.0f; xScale = Quantize(xScale, scaleQuant); yScale = Quantize(yScale, scaleQuant); xCenter = Quantize(xCenter, centerQuant); yCenter = Quantize(yCenter, centerQuant); // Optional: make depth a little less precise / less "modern perfect". zScale = Quantize(zScale, depthQuant); zTrans = Quantize(zTrans, depthQuant); Mat4 proj{}; proj.m[0] = xScale; proj.m[5] = yScale; proj.m[8] = xCenter; proj.m[9] = yCenter; proj.m[10] = zScale; proj.m[11] = -1.0f; proj.m[14] = zTrans; auto& topMat = QD3D12_CurrentMatrixStack().back(); topMat = Mat4::Multiply(topMat, proj); } void APIENTRY glDepthFunc(GLenum func) { g_gl.depthFunc = func; } void APIENTRY glColor4fv(const GLfloat* v) { if (!v) return; g_gl.curColor[0] = v[0]; g_gl.curColor[1] = v[1]; g_gl.curColor[2] = v[2]; g_gl.curColor[3] = v[3]; } void APIENTRY glTexParameterf(GLenum, GLenum pname, GLfloat param) { GLenum value = (GLenum)param; GLuint bound = g_gl.boundTexture[g_gl.activeTextureUnit]; if (bound == 0 || g_gl.textures.empty()) { switch (pname) { case GL_TEXTURE_MIN_FILTER: g_gl.defaultMinFilter = value; break; case GL_TEXTURE_MAG_FILTER: g_gl.defaultMagFilter = value; break; case GL_TEXTURE_WRAP_S: g_gl.defaultWrapS = value; break; case GL_TEXTURE_WRAP_T: g_gl.defaultWrapT = value; break; default: break; } return; } auto it = g_gl.textures.find(bound); if (it == g_gl.textures.end()) return; TextureResource& tex = it->second; switch (pname) { case GL_TEXTURE_MIN_FILTER: tex.minFilter = value; if (QD3D12_TextureWantsMipSampling(tex)) { tex.mipChainRequested = true; QD3D12_RequestAsyncMipBuild(tex); } EnsureTextureResource(tex); break; case GL_TEXTURE_MAG_FILTER: tex.magFilter = value; break; case GL_TEXTURE_WRAP_S: tex.wrapS = value; break; case GL_TEXTURE_WRAP_T: tex.wrapT = value; break; default: break; } } void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param) { glTexParameterf(target, pname, (GLfloat)param); } void APIENTRY glTexParameteriv(GLenum target, GLenum pname, const GLint* params) { if (!params) return; glTexParameteri(target, pname, params[0]); } static bool QD3D12_IsValidTexEnvMode(GLenum mode) { switch (mode) { case GL_MODULATE: case GL_REPLACE: case GL_BLEND: #ifdef GL_ADD case GL_ADD: #endif #ifdef GL_COMBINE_ARB case GL_COMBINE_ARB: #endif return true; default: return false; } } static bool QD3D12_IsValidTexCombineMode(GLenum mode) { switch (mode) { case GL_REPLACE: case GL_MODULATE: #ifdef GL_ADD case GL_ADD: #endif #ifdef GL_ADD_SIGNED_ARB case GL_ADD_SIGNED_ARB: #endif #ifdef GL_INTERPOLATE_ARB case GL_INTERPOLATE_ARB: #endif return true; default: return false; } } static bool QD3D12_IsValidTexCombineSource(GLenum source) { switch (source) { case GL_TEXTURE: #ifdef GL_PRIMARY_COLOR_ARB case GL_PRIMARY_COLOR_ARB: #endif #ifdef GL_PREVIOUS_ARB case GL_PREVIOUS_ARB: #endif #ifdef GL_CONSTANT_ARB case GL_CONSTANT_ARB: #endif return true; default: return false; } } static bool QD3D12_IsValidTexCombineOperand(GLenum operand) { switch (operand) { case GL_SRC_COLOR: case GL_ONE_MINUS_SRC_COLOR: case GL_SRC_ALPHA: case GL_ONE_MINUS_SRC_ALPHA: return true; default: return false; } } void APIENTRY glTexEnvf(GLenum target, GLenum pname, GLfloat param) { if (target != GL_TEXTURE_ENV) { g_gl.lastError = GL_INVALID_ENUM; return; } const UINT unit = ClampValue(g_gl.activeTextureUnit, 0, QD3D12_MaxTextureUnits - 1); switch (pname) { case GL_TEXTURE_ENV_MODE: { GLenum mode = (GLenum)param; if (!QD3D12_IsValidTexEnvMode(mode)) mode = GL_MODULATE; g_gl.texEnvMode[unit] = mode; break; } #ifdef GL_COMBINE_RGB_ARB case GL_COMBINE_RGB_ARB: { GLenum mode = (GLenum)param; if (!QD3D12_IsValidTexCombineMode(mode)) mode = GL_MODULATE; g_gl.texCombineRGB[unit] = mode; break; } #endif #ifdef GL_COMBINE_ALPHA_ARB case GL_COMBINE_ALPHA_ARB: { GLenum mode = (GLenum)param; if (!QD3D12_IsValidTexCombineMode(mode)) mode = GL_MODULATE; g_gl.texCombineAlpha[unit] = mode; break; } #endif #ifdef GL_SOURCE0_RGB_ARB case GL_SOURCE0_RGB_ARB: { GLenum source = (GLenum)param; if (!QD3D12_IsValidTexCombineSource(source)) source = GL_TEXTURE; g_gl.texSource0RGB[unit] = source; break; } #endif #ifdef GL_SOURCE1_RGB_ARB case GL_SOURCE1_RGB_ARB: { GLenum source = (GLenum)param; if (!QD3D12_IsValidTexCombineSource(source)) source = GL_PREVIOUS_ARB; g_gl.texSource1RGB[unit] = source; break; } #endif #ifdef GL_SOURCE0_ALPHA_ARB case GL_SOURCE0_ALPHA_ARB: { GLenum source = (GLenum)param; if (!QD3D12_IsValidTexCombineSource(source)) source = GL_TEXTURE; g_gl.texSource0Alpha[unit] = source; break; } #endif #ifdef GL_SOURCE1_ALPHA_ARB case GL_SOURCE1_ALPHA_ARB: { GLenum source = (GLenum)param; if (!QD3D12_IsValidTexCombineSource(source)) source = GL_PREVIOUS_ARB; g_gl.texSource1Alpha[unit] = source; break; } #endif #ifdef GL_OPERAND0_RGB_ARB case GL_OPERAND0_RGB_ARB: { GLenum operand = (GLenum)param; if (!QD3D12_IsValidTexCombineOperand(operand)) operand = GL_SRC_COLOR; g_gl.texOperand0RGB[unit] = operand; break; } #endif #ifdef GL_OPERAND1_RGB_ARB case GL_OPERAND1_RGB_ARB: { GLenum operand = (GLenum)param; if (!QD3D12_IsValidTexCombineOperand(operand)) operand = GL_SRC_COLOR; g_gl.texOperand1RGB[unit] = operand; break; } #endif #ifdef GL_OPERAND0_ALPHA_ARB case GL_OPERAND0_ALPHA_ARB: { GLenum operand = (GLenum)param; if (!QD3D12_IsValidTexCombineOperand(operand)) operand = GL_SRC_ALPHA; g_gl.texOperand0Alpha[unit] = operand; break; } #endif #ifdef GL_OPERAND1_ALPHA_ARB case GL_OPERAND1_ALPHA_ARB: { GLenum operand = (GLenum)param; if (!QD3D12_IsValidTexCombineOperand(operand)) operand = GL_SRC_ALPHA; g_gl.texOperand1Alpha[unit] = operand; break; } #endif #ifdef GL_RGB_SCALE_ARB case GL_RGB_SCALE_ARB: g_gl.texRGBScale[unit] = ClampValue(param, 1.0f, 4.0f); break; #endif #ifdef GL_ALPHA_SCALE case GL_ALPHA_SCALE: g_gl.texAlphaScale[unit] = ClampValue(param, 1.0f, 4.0f); break; #endif default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glTexEnvi(GLenum target, GLenum pname, GLint param) { glTexEnvf(target, pname, (GLfloat)param); } void APIENTRY glTexEnvfv(GLenum target, GLenum pname, const GLfloat* params) { if (!params) return; if (target != GL_TEXTURE_ENV) { g_gl.lastError = GL_INVALID_ENUM; return; } const UINT unit = ClampValue(g_gl.activeTextureUnit, 0, QD3D12_MaxTextureUnits - 1); if (pname == GL_TEXTURE_ENV_COLOR) { g_gl.texEnvColor[unit][0] = params[0]; g_gl.texEnvColor[unit][1] = params[1]; g_gl.texEnvColor[unit][2] = params[2]; g_gl.texEnvColor[unit][3] = params[3]; return; } glTexEnvf(target, pname, params[0]); } void APIENTRY glTexEnviv(GLenum target, GLenum pname, const GLint* params) { if (!params) return; if (target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_COLOR) { GLfloat c[4] = { (GLfloat)params[0], (GLfloat)params[1], (GLfloat)params[2], (GLfloat)params[3] }; glTexEnvfv(target, pname, c); return; } glTexEnvi(target, pname, params[0]); } void APIENTRY glTexImage2D(GLenum, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint, GLenum format, GLenum type, const GLvoid* pixels) { auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end()) return; if (level > 0) { // Ignore app-provided mip bytes. Level 1 is the legacy cue to build our // generated chain from mip 0, now on the async worker. if (level == 1) { TextureResource& tex = it->second; tex.mipChainRequested = true; QD3D12_InvalidateTextureMipChain(tex, false); QD3D12_RequestAsyncMipBuild(tex); EnsureTextureResource(tex); } return; } TextureResource& tex = it->second; tex.width = width; tex.height = height; tex.format = (format != 0) ? format : (GLenum)internalFormat; tex.compressed = false; tex.compressedInternalFormat = 0; tex.compressedBlockBytes = 0; tex.compressedImageSize = 0; tex.forceOpaqueAlpha = false; tex.dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; tex.mipLevels = QD3D12_CalcMipCount(width, height); tex.mipChainRequested = QD3D12_TextureWantsMipSampling(tex); QD3D12_InvalidateTextureMipChain(tex, true); const int bpp = BytesPerPixel(tex.format, type); tex.sysmem.resize((size_t)width * (size_t)height * (size_t)bpp); if (pixels) memcpy(tex.sysmem.data(), pixels, tex.sysmem.size()); else memset(tex.sysmem.data(), 0, tex.sysmem.size()); EnsureTextureResource(tex); } void APIENTRY glCompressedTexImage2DARB(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data) { target = QD3D12_MapCompatTextureTarget(target); if (target != GL_TEXTURE_2D) { g_gl.lastError = GL_INVALID_ENUM; return; } if (level < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end()) { g_gl.lastError = GL_INVALID_OPERATION; return; } if (level > 0) return; if (width <= 0 || height <= 0 || border != 0 || imageSize < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (!QD3D12_IsCompressedTextureFormat(internalFormat)) { g_gl.lastError = GL_INVALID_ENUM; return; } const UINT expectedSize = QD3D12_CompressedTextureImageSize(width, height, internalFormat); if ((UINT)imageSize < expectedSize) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource& tex = it->second; tex.width = width; tex.height = height; tex.format = internalFormat; tex.compressed = true; tex.compressedInternalFormat = internalFormat; tex.compressedBlockBytes = QD3D12_CompressedTextureBlockBytes(internalFormat); tex.compressedImageSize = expectedSize; tex.forceOpaqueAlpha = (internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT); tex.dxgiFormat = QD3D12_MapCompressedTextureFormat(internalFormat); tex.mipLevels = 1; tex.mipChainRequested = false; QD3D12_InvalidateTextureMipChain(tex, true); tex.sysmem.resize(expectedSize); if (data && expectedSize > 0) memcpy(tex.sysmem.data(), data, expectedSize); else if (expectedSize > 0) memset(tex.sysmem.data(), 0, expectedSize); EnsureTextureResource(tex); } void APIENTRY glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data) { glCompressedTexImage2DARB(target, level, internalFormat, width, height, border, imageSize, data); } void APIENTRY glCompressedTexImage2DEXT(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data) { glCompressedTexImage2DARB(target, level, internalFormat, width, height, border, imageSize, data); } void APIENTRY glCompressedTexSubImage2DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data) { target = QD3D12_MapCompatTextureTarget(target); if (target != GL_TEXTURE_2D) { g_gl.lastError = GL_INVALID_ENUM; return; } if (level < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end()) { g_gl.lastError = GL_INVALID_OPERATION; return; } if (level > 0) return; if (xoffset < 0 || yoffset < 0 || width <= 0 || height <= 0 || imageSize < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } TextureResource& tex = it->second; if (!tex.compressed || tex.width <= 0 || tex.height <= 0) { g_gl.lastError = GL_INVALID_OPERATION; return; } if (format != tex.compressedInternalFormat || !QD3D12_IsCompressedTextureFormat(format)) { g_gl.lastError = GL_INVALID_ENUM; return; } if (xoffset + width > tex.width || yoffset + height > tex.height) { g_gl.lastError = GL_INVALID_VALUE; return; } const bool touchesRightEdge = (xoffset + width) == tex.width; const bool touchesBottomEdge = (yoffset + height) == tex.height; if ((xoffset & 3) != 0 || (yoffset & 3) != 0 || (!touchesRightEdge && (width & 3) != 0) || (!touchesBottomEdge && (height & 3) != 0)) { g_gl.lastError = GL_INVALID_OPERATION; return; } const UINT blockBytes = QD3D12_CompressedTextureBlockBytes(format); const UINT fullBlocksWide = QD3D12_CompressedTextureBlocksWide(tex.width); const UINT subBlocksWide = QD3D12_CompressedTextureBlocksWide(width); const UINT subBlocksHigh = QD3D12_CompressedTextureBlocksHigh(height); const size_t srcRowBytes = (size_t)subBlocksWide * (size_t)blockBytes; const size_t expectedSize = srcRowBytes * (size_t)subBlocksHigh; if ((size_t)imageSize < expectedSize) { g_gl.lastError = GL_INVALID_VALUE; return; } const size_t fullSize = (size_t)QD3D12_CompressedTextureImageSize(tex.width, tex.height, tex.compressedInternalFormat); if (tex.sysmem.size() != fullSize) tex.sysmem.resize(fullSize, 0); const uint8_t* src = static_cast(data); if (!src && expectedSize > 0) { g_gl.lastError = GL_INVALID_VALUE; return; } const UINT dstBlockX = (UINT)xoffset / 4u; const UINT dstBlockY = (UINT)yoffset / 4u; for (UINT row = 0; row < subBlocksHigh; ++row) { const size_t dstOff = ((size_t)(dstBlockY + row) * (size_t)fullBlocksWide + (size_t)dstBlockX) * (size_t)blockBytes; const size_t srcOff = (size_t)row * srcRowBytes; memcpy(tex.sysmem.data() + dstOff, src + srcOff, srcRowBytes); } tex.compressedImageSize = (UINT)fullSize; QD3D12_InvalidateTextureMipChain(tex, true); } void APIENTRY glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data) { glCompressedTexSubImage2DARB(target, level, xoffset, yoffset, width, height, format, imageSize, data); } void APIENTRY glCompressedTexSubImage2DEXT(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data) { glCompressedTexSubImage2DARB(target, level, xoffset, yoffset, width, height, format, imageSize, data); } void APIENTRY glGetCompressedTexImageARB(GLenum target, GLint level, GLvoid* img) { target = QD3D12_MapCompatTextureTarget(target); if (target != GL_TEXTURE_2D) { g_gl.lastError = GL_INVALID_ENUM; return; } if (level > 0 || !img) return; auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end() || !it->second.compressed) { g_gl.lastError = GL_INVALID_OPERATION; return; } TextureResource& tex = it->second; if (!tex.sysmem.empty()) memcpy(img, tex.sysmem.data(), tex.sysmem.size()); } void APIENTRY glTexSubImage2D(GLenum, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels) { auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end() || !pixels) return; if (level > 0) { if (level == 1) { TextureResource& tex = it->second; tex.mipChainRequested = true; QD3D12_InvalidateTextureMipChain(tex, false); QD3D12_RequestAsyncMipBuild(tex); EnsureTextureResource(tex); } return; } TextureResource& tex = it->second; if (tex.width <= 0 || tex.height <= 0) return; if (tex.compressed) { g_gl.lastError = GL_INVALID_OPERATION; return; } const int bpp = BytesPerPixel(format, type); if (tex.sysmem.empty()) tex.sysmem.resize((size_t)tex.width * (size_t)tex.height * (size_t)bpp); const uint8_t* src = (const uint8_t*)pixels; for (int row = 0; row < height; ++row) { size_t dstOff = ((size_t)(yoffset + row) * (size_t)tex.width + (size_t)xoffset) * (size_t)bpp; size_t srcOff = (size_t)row * (size_t)width * (size_t)bpp; memcpy(tex.sysmem.data() + dstOff, src + srcOff, (size_t)width * (size_t)bpp); } QD3D12_InvalidateTextureMipChain(tex, true); EnsureTextureResource(tex); } void APIENTRY glCopyTexImage2D(GLenum, GLint, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint) { auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end() || width <= 0 || height <= 0) return; std::vector rgba; if (!QD3D12_ReadFramebufferRegionRGBA8(x, y, width, height, rgba) || rgba.empty()) return; TextureResource& tex = it->second; tex.width = width; tex.height = height; tex.format = QD3D12_NormalizeCopyTextureFormat(internalFormat); tex.compressed = false; tex.compressedInternalFormat = 0; tex.compressedBlockBytes = 0; tex.compressedImageSize = 0; tex.forceOpaqueAlpha = false; tex.dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; tex.mipLevels = QD3D12_CalcMipCount(width, height); tex.mipChainRequested = QD3D12_TextureWantsMipSampling(tex); QD3D12_PackRGBA8ToTextureFormat(rgba.data(), width * height, tex.format, tex.sysmem); QD3D12_InvalidateTextureMipChain(tex, true); EnsureTextureResource(tex); } void APIENTRY glCopyTexSubImage2D(GLenum, GLint, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end() || width <= 0 || height <= 0) return; TextureResource& tex = it->second; if (tex.width <= 0 || tex.height <= 0) return; if (tex.compressed) { g_gl.lastError = GL_INVALID_OPERATION; return; } if (xoffset < 0 || yoffset < 0 || xoffset + width > tex.width || yoffset + height > tex.height) return; std::vector rgba; if (!QD3D12_ReadFramebufferRegionRGBA8(x, y, width, height, rgba) || rgba.empty()) return; QD3D12_CopyRGBARegionIntoTexture(tex, xoffset, yoffset, width, height, rgba.data()); } void APIENTRY glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* data) { if (!data) return; const size_t dstBytes = (size_t)width * (size_t)height * (size_t)BytesPerPixel(format, type); if (dstBytes == 0) return; if (type != GL_UNSIGNED_BYTE) { memset(data, 0, dstBytes); return; } std::vector rgba; if (!QD3D12_ReadFramebufferRegionRGBA8(x, y, width, height, rgba) || rgba.empty()) { memset(data, 0, dstBytes); return; } std::vector packed; QD3D12_PackRGBA8ToTextureFormat(rgba.data(), width * height, format, packed); if (packed.size() < dstBytes) { memset(data, 0, dstBytes); memcpy(data, packed.data(), packed.size()); return; } memcpy(data, packed.data(), dstBytes); } void APIENTRY glDrawBuffer(GLenum mode) { g_gl.drawBuffer = mode; } void APIENTRY glReadBuffer(GLenum mode) { g_gl.readBuffer = mode; } void APIENTRY glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params) { if (!params) return; target = QD3D12_MapCompatTextureTarget(target); if (target != GL_TEXTURE_2D) { g_gl.lastError = GL_INVALID_ENUM; return; } if (level < 0) { g_gl.lastError = GL_INVALID_VALUE; *params = 0; return; } auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]); if (it == g_gl.textures.end()) { g_gl.lastError = GL_INVALID_OPERATION; return; } const TextureResource& tex = it->second; const UINT mipCount = tex.mipLevels ? tex.mipLevels : QD3D12_DesiredTextureMipLevels(tex); if ((UINT)level >= mipCount) { *params = 0; return; } const GLint mipWidth = std::max(1, tex.width >> level); const GLint mipHeight = std::max(1, tex.height >> level); switch (pname) { case GL_TEXTURE_WIDTH: *params = mipWidth; break; case GL_TEXTURE_HEIGHT: *params = mipHeight; break; case GL_TEXTURE_INTERNAL_FORMAT: *params = (GLint)tex.format; break; case GL_TEXTURE_COMPRESSED_ARB: *params = tex.compressed ? GL_TRUE : GL_FALSE; break; case GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB: *params = tex.compressed ? (GLint)tex.compressedImageSize : 0; break; default: *params = 0; break; } } void APIENTRY glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params) { if (!params) return; GLint value = 0; glGetTexLevelParameteriv(target, level, pname, &value); *params = (GLfloat)value; } // ============================================================ // SECTION 12: optional convenience for Quake code // ============================================================ void QD3D12_DrawArrays(GLenum mode, const GLVertex* verts, size_t count) { FlushImmediate(mode, verts, count); } void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) { w.sceneColorBuffers[i].Reset(); w.backBuffers[i].Reset(); w.normalBuffers[i].Reset(); w.positionBuffers[i].Reset(); w.velocityBuffers[i].Reset(); w.emissiveBuffers[i].Reset(); w.specularBuffers[i].Reset(); w.sceneColorMsaaBuffers[i].Reset(); w.normalMsaaBuffers[i].Reset(); w.positionMsaaBuffers[i].Reset(); w.velocityMsaaBuffers[i].Reset(); w.emissiveMsaaBuffers[i].Reset(); w.specularMsaaBuffers[i].Reset(); w.slOutputBuffers[i].Reset(); w.taaBuffers[i].Reset(); w.taaHistoryValid[i] = false; w.sceneColorState[i] = D3D12_RESOURCE_STATE_COMMON; w.backBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.normalBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.positionBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.velocityBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.emissiveBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.specularBufferState[i] = D3D12_RESOURCE_STATE_COMMON; w.sceneColorMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.normalMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.positionMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.velocityMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.emissiveMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.specularMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.slOutputState[i] = D3D12_RESOURCE_STATE_COMMON; w.taaBufferState[i] = D3D12_RESOURCE_STATE_COMMON; } w.depthBuffer.Reset(); w.depthState = D3D12_RESOURCE_STATE_COMMON; w.depthMsaaBuffer.Reset(); w.depthMsaaState = D3D12_RESOURCE_STATE_COMMON; w.nativeDepthBuffer.Reset(); w.nativeDepthState = D3D12_RESOURCE_STATE_COMMON; w.rtvHeap.Reset(); w.dsvHeap.Reset(); } void QD3D12_Resize() { if (!g_currentWindow) return; RECT rc{}; if (!GetClientRect(g_currentWindow->hwnd, &rc)) return; UINT width = (UINT)(rc.right - rc.left); UINT height = (UINT)(rc.bottom - rc.top); if (width == 0 || height == 0) return; if (g_currentWindow->width == width && g_currentWindow->height == height) return; if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); QD3D12_WaitForGPU(); g_gl.queuedBatches.clear(); #if defined(QD3D12_ENABLE_FFX) QD3D12_DestroyFfxUpscaleContext(); #endif QD3D12_ReleaseWindowSizeResources(*g_currentWindow); QD3D12_CHECK(g_currentWindow->swapChain->ResizeBuffers( QD3D12_FrameCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING )); g_currentWindow->width = width; g_currentWindow->height = height; QD3D12_SelectRenderResolution(*g_currentWindow, width, height); g_currentWindow->frameIndex = g_currentWindow->swapChain->GetCurrentBackBufferIndex(); g_gl.viewportW = (GLsizei)width; g_gl.viewportH = (GLsizei)height; g_gl.motionHistoryReset = true; QD3D12_CreateRTVsForWindow(*g_currentWindow); QD3D12_CreateDSVForWindow(*g_currentWindow); g_psoCache.clear(); g_arbPsoCache.clear(); g_gl.framePhase = QD3D12_FRAME_LOW_RES; g_gl.sceneResolvedThisFrame = false; g_gl.gbufferResolvedThisFrame = false; g_gl.raytracedLightingReadyThisFrame = false; g_gl.sceneFogValidThisFrame = false; QD3D12_UpdateViewportState(); } static void QD3D12_ReconfigureCurrentWindowForUpscalerChange() { if (!g_currentWindow || !g_currentWindow->swapChain || !g_gl.device) return; const UINT width = g_currentWindow->width; const UINT height = g_currentWindow->height; if (width == 0 || height == 0) return; if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); QD3D12_WaitForGPU(); g_gl.queuedBatches.clear(); #if defined(QD3D12_ENABLE_FFX) QD3D12_DestroyFfxUpscaleContext(); #endif QD3D12_ReleaseWindowSizeResources(*g_currentWindow); QD3D12_CHECK(g_currentWindow->swapChain->ResizeBuffers( QD3D12_FrameCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING )); QD3D12_SelectRenderResolution(*g_currentWindow, width, height); g_currentWindow->frameIndex = g_currentWindow->swapChain->GetCurrentBackBufferIndex(); g_gl.viewportW = (GLsizei)width; g_gl.viewportH = (GLsizei)height; g_gl.motionHistoryReset = true; QD3D12_CreateRTVsForWindow(*g_currentWindow); QD3D12_CreateDSVForWindow(*g_currentWindow); g_psoCache.clear(); g_arbPsoCache.clear(); g_gl.framePhase = QD3D12_FRAME_LOW_RES; g_gl.sceneResolvedThisFrame = false; g_gl.gbufferResolvedThisFrame = false; g_gl.raytracedLightingReadyThisFrame = false; g_gl.sceneFogValidThisFrame = false; QD3D12_UpdateViewportState(); } GLenum APIENTRY glGetError(void) { GLenum e = g_gl.lastError; if (e != GL_NO_ERROR) { g_gl.lastError = GL_NO_ERROR; return e; } return QD3D12ARB_ConsumeError(); } void APIENTRY glScissor(GLint x, GLint y, GLsizei width, GLsizei height) { g_gl.scissorX = x; g_gl.scissorY = y; g_gl.scissorW = width; g_gl.scissorH = height; } void APIENTRY glClearDepth(GLclampd depth) { g_gl.clearDepthValue = depth; } void APIENTRY glClipPlane(GLenum plane, const GLdouble* equation) { if (plane == GL_CLIP_PLANE0 && equation) { memcpy(g_gl.clipPlane0, equation, sizeof(g_gl.clipPlane0)); } } void APIENTRY glPolygonOffset(GLfloat factor, GLfloat units) { g_gl.polygonOffsetFactor = factor; g_gl.polygonOffsetUnits = units; } void APIENTRY glTexCoord2fv(const GLfloat* v) { if (!v) return; glTexCoord2f(v[0], v[1]); } void APIENTRY glColor4ubv(const GLubyte* v) { if (!v) return; g_gl.curColor[0] = v[0] / 255.0f; g_gl.curColor[1] = v[1] / 255.0f; g_gl.curColor[2] = v[2] / 255.0f; g_gl.curColor[3] = v[3] / 255.0f; } void APIENTRY glTexParameterfv(GLenum target, GLenum pname, const GLfloat* params) { if (!params) return; glTexParameterf(target, pname, params[0]); } static bool QD3D12_IsValidStencilFace(GLenum face) { return face == GL_FRONT || face == GL_BACK || face == GL_FRONT_AND_BACK; } static void QD3D12_SetStencilMaskForFace(GLenum face, GLuint mask) { if (face == GL_FRONT || face == GL_FRONT_AND_BACK) g_gl.stencilFrontMask = mask; if (face == GL_BACK || face == GL_FRONT_AND_BACK) g_gl.stencilBackMask = mask; } static void QD3D12_SetStencilFuncForFace(GLenum face, GLenum func, GLint ref, GLuint mask) { if (face == GL_FRONT || face == GL_FRONT_AND_BACK) { g_gl.stencilFrontFunc = func; g_gl.stencilFrontRef = ref; g_gl.stencilFrontFuncMask = mask; } if (face == GL_BACK || face == GL_FRONT_AND_BACK) { g_gl.stencilBackFunc = func; g_gl.stencilBackRef = ref; g_gl.stencilBackFuncMask = mask; } } static void QD3D12_SetStencilOpForFace(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) { if (face == GL_FRONT || face == GL_FRONT_AND_BACK) { g_gl.stencilFrontSFail = sfail; g_gl.stencilFrontDPFail = dpfail; g_gl.stencilFrontDPPass = dppass; } if (face == GL_BACK || face == GL_FRONT_AND_BACK) { g_gl.stencilBackSFail = sfail; g_gl.stencilBackDPFail = dpfail; g_gl.stencilBackDPPass = dppass; } } void APIENTRY glStencilMask(GLuint mask) { g_gl.stencilMask = mask; QD3D12_SetStencilMaskForFace(GL_FRONT_AND_BACK, mask); } void APIENTRY glStencilMaskSeparate(GLenum face, GLuint mask) { if (!QD3D12_IsValidStencilFace(face)) { g_gl.lastError = GL_INVALID_ENUM; return; } QD3D12_SetStencilMaskForFace(face, mask); } void APIENTRY glClearStencil(GLint s) { g_gl.clearStencilValue = s; } void APIENTRY glStencilFunc(GLenum func, GLint ref, GLuint mask) { g_gl.stencilFunc = func; g_gl.stencilRef = ref; g_gl.stencilFuncMask = mask; QD3D12_SetStencilFuncForFace(GL_FRONT_AND_BACK, func, ref, mask); } void APIENTRY glStencilFuncSeparateATI(GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask) { QD3D12_SetStencilFuncForFace(GL_FRONT, frontfunc, ref, mask); QD3D12_SetStencilFuncForFace(GL_BACK, backfunc, ref, mask); } void APIENTRY glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) { g_gl.stencilSFail = sfail; g_gl.stencilDPFail = dpfail; g_gl.stencilDPPass = dppass; const GLenum face = g_gl.stencilTwoSide ? g_gl.activeStencilFace : GL_FRONT_AND_BACK; QD3D12_SetStencilOpForFace(face, sfail, dpfail, dppass); } void APIENTRY glStencilOpSeparateATI(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) { if (!QD3D12_IsValidStencilFace(face)) { g_gl.lastError = GL_INVALID_ENUM; return; } QD3D12_SetStencilOpForFace(face, sfail, dpfail, dppass); } void APIENTRY glActiveStencilFaceEXT(GLenum face) { if (face != GL_FRONT && face != GL_BACK) { g_gl.lastError = GL_INVALID_ENUM; return; } g_gl.activeStencilFace = face; } void APIENTRY glDepthBoundsEXT(GLclampd zmin, GLclampd zmax) { g_gl.depthBoundsMin = ClampValue(zmin, 0.0, 1.0); g_gl.depthBoundsMax = ClampValue(zmax, 0.0, 1.0); if (g_gl.depthBoundsMax < g_gl.depthBoundsMin) std::swap(g_gl.depthBoundsMin, g_gl.depthBoundsMax); } void APIENTRY glColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a) { g_gl.colorMaskR = r; g_gl.colorMaskG = g; g_gl.colorMaskB = b; g_gl.colorMaskA = a; } void APIENTRY glClientActiveTextureARB(GLenum texture) { if (texture >= GL_TEXTURE0_ARB) g_gl.clientActiveTextureUnit = ClampValue((GLuint)(texture - GL_TEXTURE0_ARB), 0, QD3D12_MaxTextureUnits - 1); else g_gl.clientActiveTextureUnit = 0; } void APIENTRY glLockArraysEXT(GLint first, GLsizei count) { (void)first; (void)count; } void APIENTRY glUnlockArraysEXT(void) { } void APIENTRY glNormalPointer(GLenum type, GLsizei stride, const void* pointer) { g_gl.normalArray.size = 3; g_gl.normalArray.type = type; g_gl.normalArray.stride = stride; g_gl.normalArray.ptr = QD3D12_ResolveArrayPointer(pointer); } void APIENTRY glTangentPointer(GLenum type, GLsizei stride, const void* pointer) { g_gl.tangentArray.size = 3; g_gl.tangentArray.type = type; g_gl.tangentArray.stride = stride; g_gl.tangentArray.ptr = QD3D12_ResolveArrayPointer(pointer); } void APIENTRY glBinormalPointer(GLenum type, GLsizei stride, const void* pointer) { g_gl.bitangentArray.size = 3; g_gl.bitangentArray.type = type; g_gl.bitangentArray.stride = stride; g_gl.bitangentArray.ptr = QD3D12_ResolveArrayPointer(pointer); } void APIENTRY glNormalBuffer(GLenum type, GLsizei stride, const void* pointer) { glNormalPointer(type, stride, pointer); g_gl.normalArray.enabled = true; } void APIENTRY glTangentBuffer(GLenum type, GLsizei stride, const void* pointer) { glTangentPointer(type, stride, pointer); g_gl.tangentArray.enabled = true; } void APIENTRY glBinormalBuffer(GLenum type, GLsizei stride, const void* pointer) { glBinormalPointer(type, stride, pointer); g_gl.bitangentArray.enabled = true; } void APIENTRY glEnableClientState(GLenum array) { switch (array) { case GL_VERTEX_ARRAY: g_gl.vertexArray.enabled = true; break; case GL_NORMAL_ARRAY: g_gl.normalArray.enabled = true; break; case GL_TANGENT_ARRAY_QD3D12: g_gl.tangentArray.enabled = true; break; case GL_BINORMAL_ARRAY_QD3D12: g_gl.bitangentArray.enabled = true; break; case GL_COLOR_ARRAY: g_gl.colorArray.enabled = true; break; case GL_TEXTURE_COORD_ARRAY: g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled = true; break; default: break; } } void APIENTRY glDisableClientState(GLenum array) { switch (array) { case GL_VERTEX_ARRAY: g_gl.vertexArray.enabled = false; break; case GL_NORMAL_ARRAY: g_gl.normalArray.enabled = false; break; case GL_TANGENT_ARRAY_QD3D12: g_gl.tangentArray.enabled = false; break; case GL_BINORMAL_ARRAY_QD3D12: g_gl.bitangentArray.enabled = false; break; case GL_COLOR_ARRAY: g_gl.colorArray.enabled = false; break; case GL_TEXTURE_COORD_ARRAY: g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled = false; break; default: break; } } void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr) { g_gl.vertexArray.size = size; g_gl.vertexArray.type = type; g_gl.vertexArray.stride = stride; g_gl.vertexArray.ptr = QD3D12_ResolveArrayPointer(ptr); } void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr) { g_gl.colorArray.size = size; g_gl.colorArray.type = type; g_gl.colorArray.stride = stride; g_gl.colorArray.ptr = QD3D12_ResolveArrayPointer(ptr); } void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr) { auto& tc = g_gl.texCoordArray[g_gl.clientActiveTextureUnit]; tc.size = size; tc.type = type; tc.stride = stride; tc.ptr = QD3D12_ResolveArrayPointer(ptr); } void APIENTRY glArrayElement(GLint i) { GLVertex& v = g_gl.immediateVerts.Push(); QD3D12_FetchArrayVertex(i, v); } void APIENTRY glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) { if (count <= 0) return; const void* resolvedIndices = QD3D12_ResolveElementPointer(indices, type, count); if (!resolvedIndices) { g_gl.lastError = GL_INVALID_OPERATION; return; } g_gl.currentPrim = mode; g_gl.immediateVerts.Clear(); switch (type) { case GL_UNSIGNED_INT: { const GLuint* idx = static_cast(resolvedIndices); for (GLsizei i = 0; i < count; ++i) { GLVertex& v = g_gl.immediateVerts.Push(); QD3D12_FetchArrayVertex(static_cast(idx[i]), v); } break; } case GL_UNSIGNED_SHORT: { const GLushort* idx = static_cast(resolvedIndices); for (GLsizei i = 0; i < count; ++i) { GLVertex& v = g_gl.immediateVerts.Push(); QD3D12_FetchArrayVertex(static_cast(idx[i]), v); } break; } case GL_UNSIGNED_BYTE: { const GLubyte* idx = static_cast(resolvedIndices); for (GLsizei i = 0; i < count; ++i) { GLVertex& v = g_gl.immediateVerts.Push(); QD3D12_FetchArrayVertex(static_cast(idx[i]), v); } break; } default: g_gl.lastError = GL_INVALID_ENUM; return; } if (!g_gl.colorArray.enabled) { for (int i = 0; i < g_gl.immediateVerts.Size(); i++) { g_gl.immediateVerts.Data()[i].r = g_gl.curColor[0]; g_gl.immediateVerts.Data()[i].g = g_gl.curColor[1]; g_gl.immediateVerts.Data()[i].b = g_gl.curColor[2]; g_gl.immediateVerts.Data()[i].a = g_gl.curColor[3]; } } FlushImmediate(mode, g_gl.immediateVerts.Data(), g_gl.immediateVerts.Size()); } static inline int QD3D12_CompatAttribTexUnit(GLuint index) { switch (index) { case 8: return 0; // Doom 3 base texture coordinate default: return -1; } } static inline bool QD3D12_CompatAttribIsNormal(GLuint index) { return (index == 2 || index == 9); } static inline bool QD3D12_CompatAttribIsTangent(GLuint index) { return index == 10; } static inline bool QD3D12_CompatAttribIsBitangent(GLuint index) { return index == 11; } void APIENTRY glVertexAttribPointerARB(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer) { (void)normalized; if (index == 0) { glVertexPointer(size, type, stride, pointer); return; } if (index == 3) { glColorPointer(size, type, stride, pointer); return; } if (QD3D12_CompatAttribIsNormal(index)) { glNormalPointer(type, stride, pointer); return; } if (QD3D12_CompatAttribIsTangent(index)) { glTangentPointer(type, stride, pointer); return; } if (QD3D12_CompatAttribIsBitangent(index)) { glBinormalPointer(type, stride, pointer); return; } const int texUnit = QD3D12_CompatAttribTexUnit(index); if (texUnit >= 0 && texUnit < (int)QD3D12_MaxTextureUnits) { const GLuint oldUnit = g_gl.clientActiveTextureUnit; glClientActiveTextureARB(GL_TEXTURE0_ARB + (GLenum)texUnit); glTexCoordPointer(size, type, stride, pointer); glClientActiveTextureARB(GL_TEXTURE0_ARB + oldUnit); return; } // Best-effort compatibility only: unsupported generic attributes are intentionally ignored. } void APIENTRY glEnableVertexAttribArrayARB(GLuint index) { if (index == 0) { glEnableClientState(GL_VERTEX_ARRAY); return; } if (index == 3) { glEnableClientState(GL_COLOR_ARRAY); return; } if (QD3D12_CompatAttribIsNormal(index)) { glEnableClientState(GL_NORMAL_ARRAY); return; } if (QD3D12_CompatAttribIsTangent(index)) { g_gl.tangentArray.enabled = true; return; } if (QD3D12_CompatAttribIsBitangent(index)) { g_gl.bitangentArray.enabled = true; return; } const int texUnit = QD3D12_CompatAttribTexUnit(index); if (texUnit >= 0 && texUnit < (int)QD3D12_MaxTextureUnits) { const GLuint oldUnit = g_gl.clientActiveTextureUnit; glClientActiveTextureARB(GL_TEXTURE0_ARB + (GLenum)texUnit); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB + oldUnit); return; } } void APIENTRY glDisableVertexAttribArrayARB(GLuint index) { if (index == 0) { glDisableClientState(GL_VERTEX_ARRAY); return; } if (index == 3) { glDisableClientState(GL_COLOR_ARRAY); return; } if (QD3D12_CompatAttribIsNormal(index)) { glDisableClientState(GL_NORMAL_ARRAY); return; } if (QD3D12_CompatAttribIsTangent(index)) { g_gl.tangentArray.enabled = false; return; } if (QD3D12_CompatAttribIsBitangent(index)) { g_gl.bitangentArray.enabled = false; return; } const int texUnit = QD3D12_CompatAttribTexUnit(index); if (texUnit >= 0 && texUnit < (int)QD3D12_MaxTextureUnits) { const GLuint oldUnit = g_gl.clientActiveTextureUnit; glClientActiveTextureARB(GL_TEXTURE0_ARB + (GLenum)texUnit); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB + oldUnit); return; } } static inline GLenum QD3D12_MapCompatBufferTarget(GLenum target) { switch (target) { case GL_ARRAY_BUFFER_ARB: return GL_ARRAY_BUFFER; case GL_ELEMENT_ARRAY_BUFFER_ARB: return GL_ELEMENT_ARRAY_BUFFER; default: return target; } } static inline GLenum QD3D12_MapCompatTextureTarget(GLenum target) { switch (target) { case GL_TEXTURE_3D: case GL_TEXTURE_CUBE_MAP_EXT: case GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT: case GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT: case GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT: case GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT: return GL_TEXTURE_2D; default: return target; } } // ============================================================ // Minimal WGL pbuffer compatibility // Supports the rvTexRenderTarget flow: // choose pixel format -> create pbuffer/DC/context -> make current -> render -> bind tex image. // ============================================================ #ifndef WGL_ARB_pbuffer DECLARE_HANDLE(HPBUFFERARB); #endif #ifndef WGL_DRAW_TO_PBUFFER_ARB #define WGL_DRAW_TO_PBUFFER_ARB 0x202D #endif #ifndef WGL_PBUFFER_WIDTH_ARB #define WGL_PBUFFER_WIDTH_ARB 0x2034 #endif #ifndef WGL_PBUFFER_HEIGHT_ARB #define WGL_PBUFFER_HEIGHT_ARB 0x2035 #endif #ifndef WGL_PBUFFER_LOST_ARB #define WGL_PBUFFER_LOST_ARB 0x2036 #endif #ifndef WGL_BIND_TO_TEXTURE_RGB_ARB #define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 #endif #ifndef WGL_BIND_TO_TEXTURE_RGBA_ARB #define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 #endif #ifndef WGL_TEXTURE_FORMAT_ARB #define WGL_TEXTURE_FORMAT_ARB 0x2072 #endif #ifndef WGL_TEXTURE_TARGET_ARB #define WGL_TEXTURE_TARGET_ARB 0x2073 #endif #ifndef WGL_MIPMAP_TEXTURE_ARB #define WGL_MIPMAP_TEXTURE_ARB 0x2074 #endif #ifndef WGL_TEXTURE_RGB_ARB #define WGL_TEXTURE_RGB_ARB 0x2075 #endif #ifndef WGL_TEXTURE_RGBA_ARB #define WGL_TEXTURE_RGBA_ARB 0x2076 #endif #ifndef WGL_TEXTURE_CUBE_MAP_ARB #define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 #endif #ifndef WGL_TEXTURE_2D_ARB #define WGL_TEXTURE_2D_ARB 0x207A #endif #ifndef WGL_MIPMAP_LEVEL_ARB #define WGL_MIPMAP_LEVEL_ARB 0x207B #endif #ifndef WGL_CUBE_MAP_FACE_ARB #define WGL_CUBE_MAP_FACE_ARB 0x207C #endif #ifndef WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB #define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D #endif #ifndef WGL_FRONT_LEFT_ARB #define WGL_FRONT_LEFT_ARB 0x2083 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 #endif #ifndef WGL_TEXTURE_RECTANGLE_NV #define WGL_TEXTURE_RECTANGLE_NV 0x20A2 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 #endif #ifndef WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 #endif #ifndef WGL_FLOAT_COMPONENTS_NV #define WGL_FLOAT_COMPONENTS_NV 0x20B0 #endif struct QD3D12GLContext { HDC dc = nullptr; QD3D12Window* window = nullptr; }; struct QD3D12Pbuffer { QD3D12Window window; HDC dc = nullptr; int width = 0; int height = 0; int textureTarget = WGL_TEXTURE_2D_ARB; int textureFormat = WGL_TEXTURE_RGBA_ARB; int cubeFace = WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; GLuint boundTexture = 0; }; static std::unordered_map g_qd3d12DcToWindow; static std::unordered_map g_qd3d12Contexts; static std::unordered_map g_qd3d12Pbuffers; static void QD3D12_RegisterWindowDC(QD3D12Window* w) { if (w == NULL) { return; } const HDC hdc = w->hdc; if (hdc == NULL) { return; } // If the HDC is already registered, only update the pointer. // This avoids insert/rehash for the common case. std::unordered_map::iterator it = g_qd3d12DcToWindow.find(hdc); if (it != g_qd3d12DcToWindow.end()) { it->second = w; return; } // Avoid tiny-map rehash behavior in release builds. if (g_qd3d12DcToWindow.bucket_count() < 256) { g_qd3d12DcToWindow.reserve(256); } g_qd3d12DcToWindow.emplace(hdc, w); } static void QD3D12_UnregisterWindowDC(HDC dc) { if (dc) g_qd3d12DcToWindow.erase(dc); } static QD3D12Window* QD3D12_FindWindowForDC(HDC dc) { if (!dc) return nullptr; auto it = g_qd3d12DcToWindow.find(dc); if (it != g_qd3d12DcToWindow.end()) return it->second; for (auto& kv : g_windows) { if (kv.second.hdc == dc) { g_qd3d12DcToWindow[dc] = &kv.second; return &kv.second; } } return nullptr; } static HGLRC QD3D12_CreateGLContextHandle(HDC dc, QD3D12Window* window) { QD3D12GLContext* ctx = new QD3D12GLContext(); ctx->dc = dc; ctx->window = window ? window : QD3D12_FindWindowForDC(dc); HGLRC handle = reinterpret_cast(ctx); g_qd3d12Contexts[handle] = ctx; return handle; } static QD3D12GLContext* QD3D12_GetGLContext(HGLRC rc) { auto it = g_qd3d12Contexts.find(rc); if (it == g_qd3d12Contexts.end()) return nullptr; return it->second; } static QD3D12Pbuffer* QD3D12_GetPbuffer(HPBUFFERARB handle) { auto it = g_qd3d12Pbuffers.find(handle); if (it == g_qd3d12Pbuffers.end()) return nullptr; return it->second; } static int QD3D12_FindAttribInt(const int* attribs, int attrib, int defaultValue) { if (!attribs) return defaultValue; for (const int* p = attribs; p[0] != 0; p += 2) { if (p[0] == attrib) return p[1]; } return defaultValue; } static bool QD3D12_InitPbufferWindow(QD3D12Pbuffer& pb, int width, int height) { if (!g_gl.device || !g_gl.srvHeap || !g_gl.cmdList || !g_gl.fence) return false; QD3D12Window& w = pb.window; w = QD3D12Window{}; w.isPbuffer = true; w.hwnd = nullptr; w.hdc = pb.dc; w.ownsHdc = false; w.width = (UINT)max(1, width); w.height = (UINT)max(1, height); w.renderWidth = w.width; w.renderHeight = w.height; w.frameIndex = 0; QD3D12_CreateSurfaceFrameResources(w); QD3D12_CreateRTVsForWindow(w); QD3D12_CreateDSVForWindow(w); QD3D12_CreateUploadRingForWindow(w); w.viewport.TopLeftX = 0.0f; w.viewport.TopLeftY = 0.0f; w.viewport.Width = (float)w.width; w.viewport.Height = (float)w.height; w.viewport.MinDepth = 0.0f; w.viewport.MaxDepth = 1.0f; w.scissor.left = 0; w.scissor.top = 0; w.scissor.right = (LONG)w.width; w.scissor.bottom = (LONG)w.height; QD3D12_RegisterWindowDC(&w); return true; } static void QD3D12_DestroyPbufferWindow(QD3D12Pbuffer& pb) { QD3D12Window& w = pb.window; if ((g_gl.frameOpen && g_gl.frameOwner == &w) || g_currentWindow == &w) QD3D12_SubmitOpenFrameNoPresentAndWait(); if (g_currentWindow == &w) g_currentWindow = nullptr; QD3D12_WaitForGPU(); QD3D12_UnregisterWindowDC(w.hdc); QD3D12_DestroyUploadRingForWindow(w); QD3D12_ReleaseWindowSizeResources(w); for (UINT i = 0; i < QD3D12_FrameCount; ++i) w.frames[i].cmdAlloc.Reset(); } static bool QD3D12_CopyPbufferToBoundTexture(QD3D12Pbuffer& pb) { GLuint textureName = g_gl.boundTexture[g_gl.activeTextureUnit]; if (textureName == 0) { g_gl.lastError = GL_INVALID_OPERATION; return false; } auto it = g_gl.textures.find(textureName); if (it == g_gl.textures.end()) { TextureResource tex{}; tex.glId = textureName; tex.srvIndex = UINT_MAX; tex.minFilter = g_gl.defaultMinFilter; tex.magFilter = g_gl.defaultMagFilter; tex.wrapS = g_gl.defaultWrapS; tex.wrapT = g_gl.defaultWrapT; it = g_gl.textures.emplace(textureName, std::move(tex)).first; } TextureResource& tex = it->second; tex.width = pb.width; tex.height = pb.height; tex.format = GL_RGBA; tex.compressed = false; tex.compressedInternalFormat = 0; tex.compressedBlockBytes = 0; tex.compressedImageSize = 0; tex.forceOpaqueAlpha = false; tex.dxgiFormat = QD3D12_SceneColorFormat; tex.mipLevels = 1; tex.sysmem.clear(); QD3D12Window* savedWindow = g_currentWindow; HDC savedDC = g_qd3d12CurrentDC; HGLRC savedRC = g_qd3d12CurrentRC; g_currentWindow = &pb.window; g_qd3d12CurrentDC = pb.dc; if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); QD3D12Window& w = pb.window; w.frameIndex = 0; QD3D12_WaitForFrame(w.frameIndex); QD3D12_CHECK(w.frames[w.frameIndex].cmdAlloc->Reset()); QD3D12_CHECK(g_gl.cmdList->Reset(w.frames[w.frameIndex].cmdAlloc.Get(), nullptr)); EnsureTextureResource(tex); if (!tex.texture) { g_currentWindow = savedWindow; g_qd3d12CurrentDC = savedDC; g_qd3d12CurrentRC = savedRC; return false; } ID3D12Resource* src = w.backBuffers[w.frameIndex].Get(); ID3D12Resource* dst = tex.texture.Get(); if (!src || !dst) { g_currentWindow = savedWindow; g_qd3d12CurrentDC = savedDC; g_qd3d12CurrentRC = savedRC; return false; } QD3D12_TransitionResource(g_gl.cmdList.Get(), src, w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_COPY_SOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), dst, tex.state, D3D12_RESOURCE_STATE_COPY_DEST); D3D12_TEXTURE_COPY_LOCATION srcLoc{}; srcLoc.pResource = src; srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; srcLoc.SubresourceIndex = 0; D3D12_TEXTURE_COPY_LOCATION dstLoc{}; dstLoc.pResource = dst; dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLoc.SubresourceIndex = 0; g_gl.cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); QD3D12_TransitionResource(g_gl.cmdList.Get(), dst, tex.state, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(g_gl.cmdList.Get(), src, w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PRESENT); QD3D12_CHECK(g_gl.cmdList->Close()); ID3D12CommandList* lists[] = { g_gl.cmdList.Get() }; g_gl.queue->ExecuteCommandLists(1, lists); const UINT64 signalValue = g_gl.nextFenceValue++; QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue)); w.frames[w.frameIndex].fenceValue = signalValue; if (g_gl.fence->GetCompletedValue() < signalValue) { QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(signalValue, g_gl.fenceEvent)); WaitForSingleObject(g_gl.fenceEvent, INFINITE); } tex.gpuValid = true; pb.boundTexture = textureName; g_currentWindow = savedWindow; g_qd3d12CurrentDC = savedDC; g_qd3d12CurrentRC = savedRC; return true; } //HDC WINAPI wglGetCurrentDC(void) //{ // return g_qd3d12CurrentDC; //} BOOL WINAPI wglDeleteContext(HGLRC hglrc) { if (!hglrc) return TRUE; if (g_qd3d12CurrentRC == hglrc) g_qd3d12CurrentRC = nullptr; auto it = g_qd3d12Contexts.find(hglrc); if (it != g_qd3d12Contexts.end()) { delete it->second; g_qd3d12Contexts.erase(it); } return TRUE; } BOOL WINAPI wglShareLists(HGLRC, HGLRC) { // The shim keeps GL objects in global CPU/D3D12 state, so contexts already share resources. return TRUE; } const char* WINAPI wglGetExtensionsStringARB(HDC) { return "WGL_ARB_pixel_format WGL_ARB_pbuffer WGL_ARB_render_texture WGL_NV_render_texture_rectangle WGL_NV_float_buffer"; } const char* WINAPI wglGetExtensionsStringEXT(void) { return wglGetExtensionsStringARB(g_qd3d12CurrentDC); } BOOL WINAPI wglMakeCurrent(HDC hdc, HGLRC hglrc) { if (!hdc && !hglrc) { if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); g_qd3d12CurrentDC = nullptr; g_qd3d12CurrentRC = nullptr; g_currentWindow = nullptr; return TRUE; } QD3D12Window* window = nullptr; if (QD3D12GLContext* ctx = QD3D12_GetGLContext(hglrc)) window = ctx->window; if (!window) window = QD3D12_FindWindowForDC(hdc); if (!window) return FALSE; g_qd3d12CurrentDC = hdc; g_qd3d12CurrentRC = hglrc; QD3D12_SetCurrentWindow(window); return TRUE; } BOOL WINAPI wglChoosePixelFormatARB(HDC, const int*, const FLOAT*, UINT nMaxFormats, int* piFormats, UINT* nNumFormats) { if (nNumFormats) *nNumFormats = (nMaxFormats > 0 && piFormats) ? 1u : 0u; if (piFormats && nMaxFormats > 0) piFormats[0] = 1; return TRUE; } BOOL WINAPI wglGetPixelFormatAttribivARB(HDC, int, int, UINT nAttributes, const int* piAttributes, int* piValues) { if (!piAttributes || !piValues) return FALSE; for (UINT i = 0; i < nAttributes; ++i) { switch (piAttributes[i]) { case WGL_DRAW_TO_PBUFFER_ARB: case WGL_BIND_TO_TEXTURE_RGB_ARB: case WGL_BIND_TO_TEXTURE_RGBA_ARB: case WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV: case WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV: case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV: case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV: case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV: case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV: case WGL_FLOAT_COMPONENTS_NV: piValues[i] = 1; break; default: piValues[i] = 0; break; } } return TRUE; } HPBUFFERARB WINAPI wglCreatePbufferARB(HDC, int, int iWidth, int iHeight, const int* piAttribList) { if (iWidth <= 0 || iHeight <= 0) return nullptr; QD3D12Pbuffer* pb = new QD3D12Pbuffer(); pb->width = iWidth; pb->height = iHeight; pb->textureTarget = QD3D12_FindAttribInt(piAttribList, WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_2D_ARB); pb->textureFormat = QD3D12_FindAttribInt(piAttribList, WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_RGBA_ARB); pb->cubeFace = WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; pb->dc = reinterpret_cast(pb); if (!QD3D12_InitPbufferWindow(*pb, iWidth, iHeight)) { delete pb; return nullptr; } HPBUFFERARB handle = reinterpret_cast(pb); g_qd3d12Pbuffers[handle] = pb; return handle; } HDC WINAPI wglGetPbufferDCARB(HPBUFFERARB hPbuffer) { QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); return pb ? pb->dc : nullptr; } int WINAPI wglReleasePbufferDCARB(HPBUFFERARB hPbuffer, HDC hdc) { QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); return (pb && pb->dc == hdc) ? 1 : 0; } BOOL WINAPI wglDestroyPbufferARB(HPBUFFERARB hPbuffer) { QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); if (!pb) return FALSE; for (auto it = g_qd3d12Contexts.begin(); it != g_qd3d12Contexts.end(); ) { if (it->second && it->second->window == &pb->window) { if (g_qd3d12CurrentRC == it->first) g_qd3d12CurrentRC = nullptr; delete it->second; it = g_qd3d12Contexts.erase(it); } else { ++it; } } QD3D12_DestroyPbufferWindow(*pb); g_qd3d12Pbuffers.erase(hPbuffer); delete pb; return TRUE; } BOOL WINAPI wglQueryPbufferARB(HPBUFFERARB hPbuffer, int iAttribute, int* piValue) { if (!piValue) return FALSE; QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); if (!pb) return FALSE; switch (iAttribute) { case WGL_PBUFFER_WIDTH_ARB: *piValue = pb->width; return TRUE; case WGL_PBUFFER_HEIGHT_ARB: *piValue = pb->height; return TRUE; case WGL_PBUFFER_LOST_ARB: *piValue = 0; return TRUE; case WGL_TEXTURE_TARGET_ARB: *piValue = pb->textureTarget; return TRUE; case WGL_TEXTURE_FORMAT_ARB: *piValue = pb->textureFormat; return TRUE; case WGL_CUBE_MAP_FACE_ARB: *piValue = pb->cubeFace; return TRUE; default: *piValue = 0; return TRUE; } } BOOL WINAPI wglSetPbufferAttribARB(HPBUFFERARB hPbuffer, const int* piAttribList) { QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); if (!pb) return FALSE; if (!piAttribList) return TRUE; for (const int* p = piAttribList; p[0] != 0; p += 2) { if (p[0] == WGL_CUBE_MAP_FACE_ARB) pb->cubeFace = p[1]; else if (p[0] == WGL_MIPMAP_LEVEL_ARB) ; // ignored; this shim only backs level 0 } return TRUE; } BOOL WINAPI wglBindTexImageARB(HPBUFFERARB hPbuffer, int iBuffer) { if (iBuffer != WGL_FRONT_LEFT_ARB) return FALSE; QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); if (!pb) return FALSE; return QD3D12_CopyPbufferToBoundTexture(*pb) ? TRUE : FALSE; } BOOL WINAPI wglReleaseTexImageARB(HPBUFFERARB hPbuffer, int iBuffer) { if (iBuffer != WGL_FRONT_LEFT_ARB) return FALSE; QD3D12Pbuffer* pb = QD3D12_GetPbuffer(hPbuffer); if (!pb) return FALSE; pb->boundTexture = 0; return TRUE; } BOOL WINAPI wglSwapBuffers(HDC hdc) { QD3D12_SwapBuffers(hdc); return TRUE; } void APIENTRY glFlush(void) { glFinish(); } void APIENTRY glFrontFace(GLenum mode) { if (mode != GL_CW && mode != GL_CCW) { g_gl.lastError = GL_INVALID_ENUM; return; } g_gl.frontFace = mode; } void APIENTRY glMaterialfv(GLenum face, GLenum pname, const GLfloat* params) { (void)face; if (!params) return; glMaterialf(face, pname, params[0]); } void APIENTRY glMaterialf(GLenum face, GLenum pname, GLfloat param) { (void)face; switch (pname) { case GL_QD3D12_MATERIAL_GLASS: glGlassMaterialQD3D12((param != 0.0f) ? GL_TRUE : GL_FALSE); return; case GL_QD3D12_MATERIAL_FLAGS: glRaytracingMaterialFlagsQD3D12((GLuint)max(0.0f, floorf(param + 0.5f))); return; default: return; } } void APIENTRY glLightfv(GLenum light, GLenum pname, const GLfloat* params) { (void)light; (void)pname; (void)params; } void APIENTRY glLightf(GLenum light, GLenum pname, GLfloat param) { (void)light; (void)pname; (void)param; } void APIENTRY glActiveTexture(GLenum texture) { glActiveTextureARB(texture); } void APIENTRY glClientActiveTexture(GLenum texture) { glClientActiveTextureARB(texture); } void APIENTRY glMultiTexCoord2f(GLenum texture, GLfloat s, GLfloat t) { glMultiTexCoord2fARB(texture, s, t); } void APIENTRY glGenBuffersARB(GLsizei n, GLuint* buffers) { glGenBuffers(n, buffers); } void APIENTRY glDeleteBuffersARB(GLsizei n, const GLuint* buffers) { glDeleteBuffers(n, buffers); } void APIENTRY glBindBufferARB(GLenum target, GLuint buffer) { glBindBuffer(QD3D12_MapCompatBufferTarget(target), buffer); } void APIENTRY glBufferDataARB(GLenum target, GLsizeiptrARB size, const void* data, GLenum usage) { glBufferData(QD3D12_MapCompatBufferTarget(target), (GLsizeiptr)size, data, usage); } void APIENTRY glBufferSubDataARB(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void* data) { glBufferSubData(QD3D12_MapCompatBufferTarget(target), (GLintptr)offset, (GLsizeiptr)size, data); } void* APIENTRY glMapBufferARB(GLenum target, GLenum access) { target = QD3D12_MapCompatBufferTarget(target); GLuint bound = 0; switch (target) { case GL_ARRAY_BUFFER: bound = g_gl.boundArrayBuffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound = g_gl.boundElementArrayBuffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return nullptr; } GLBufferObject* bo = QD3D12_GetBuffer(bound); if (!bo) { g_gl.lastError = GL_INVALID_OPERATION; return nullptr; } GLbitfield flags = 0; switch (access) { case GL_READ_ONLY: flags = GL_MAP_READ_BIT; break; case GL_WRITE_ONLY: flags = GL_MAP_WRITE_BIT; break; case GL_READ_WRITE: default: flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT; break; } return glMapBufferRange(target, 0, (GLsizeiptr)bo->data.size(), flags); } GLboolean APIENTRY glUnmapBufferARB(GLenum target) { return glUnmapBuffer(QD3D12_MapCompatBufferTarget(target)); } void APIENTRY glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels) { if (depth <= 0) { g_gl.lastError = GL_INVALID_VALUE; return; } glTexImage2D(QD3D12_MapCompatTextureTarget(target), level, internalFormat, width, height, border, format, type, pixels); } void APIENTRY glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels) { (void)zoffset; if (depth <= 0) { g_gl.lastError = GL_INVALID_VALUE; return; } glTexSubImage2D(QD3D12_MapCompatTextureTarget(target), level, xoffset, yoffset, width, height, format, type, pixels); } void APIENTRY glColorTableEXT(GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid* data) { (void)target; (void)internalformat; (void)width; (void)format; (void)type; (void)data; } PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { if (!name) return nullptr; struct ProcMap { const char* name; PROC proc; }; static const ProcMap table[] = { { "glActiveTextureARB", (PROC)glActiveTextureARB }, { "glClientActiveTextureARB", (PROC)glClientActiveTextureARB }, { "glMultiTexCoord2fARB", (PROC)glMultiTexCoord2fARB }, { "glSelectTextureSGIS", (PROC)glSelectTextureSGIS }, { "glMTexCoord2fSGIS", (PROC)glMTexCoord2fSGIS }, { "glLockArraysEXT", (PROC)glLockArraysEXT }, { "glUnlockArraysEXT", (PROC)glUnlockArraysEXT }, { "glVertexAttribPointerARB", (PROC)glVertexAttribPointerARB }, { "glEnableVertexAttribArrayARB", (PROC)glEnableVertexAttribArrayARB }, { "glDisableVertexAttribArrayARB", (PROC)glDisableVertexAttribArrayARB }, { "glTangentPointer", (PROC)glTangentPointer }, { "glBinormalPointer", (PROC)glBinormalPointer }, { "glNormalBuffer", (PROC)glNormalBuffer }, { "glTangentBuffer", (PROC)glTangentBuffer }, { "glBinormalBuffer", (PROC)glBinormalBuffer }, { "glTangent3f", (PROC)glTangent3f }, { "glTangent3fv", (PROC)glTangent3fv }, { "glBinormal3f", (PROC)glBinormal3f }, { "glBinormal3fv", (PROC)glBinormal3fv }, { "glTagTextureNormalMap", (PROC)glTagTextureNormalMap }, { "glTextureNormalMap", (PROC)glTextureNormalMap }, { "glBindNormalMapTexture", (PROC)glBindNormalMapTexture }, { "glNormalMapTexture", (PROC)glNormalMapTexture }, { "glNormalMapStrengthf", (PROC)glNormalMapStrengthf }, { "glNormalMapYSignf", (PROC)glNormalMapYSignf }, { "glNeuralPOMMaterialQD3D12", (PROC)glNeuralPOMMaterialQD3D12 }, { "glNeuralPOMMaterialFromBoundTextureQD3D12", (PROC)glNeuralPOMMaterialFromBoundTextureQD3D12 }, { "glClearNeuralPOMMaterialQD3D12", (PROC)glClearNeuralPOMMaterialQD3D12 }, { "glBindNeuralPOMTextureQD3D12", (PROC)glBindNeuralPOMTextureQD3D12 }, { "glNeuralPOMTextureQD3D12", (PROC)glNeuralPOMTextureQD3D12 }, { "glEnableNeuralPOMQD3D12", (PROC)glEnableNeuralPOMQD3D12 }, { "glTagTextureGlowMap", (PROC)glTagTextureGlowMap }, { "glTextureGlowMap", (PROC)glTextureGlowMap }, { "glBindGlowMapTexture", (PROC)glBindGlowMapTexture }, { "glGlowMapTexture", (PROC)glGlowMapTexture }, { "glGlowMapStrengthf", (PROC)glGlowMapStrengthf }, { "glTagTextureSpecularMap", (PROC)glTagTextureSpecularMap }, { "glTextureSpecularMap", (PROC)glTextureSpecularMap }, { "glBindSpecularMapTexture", (PROC)glBindSpecularMapTexture }, { "glSpecularMapTexture", (PROC)glSpecularMapTexture }, { "glSpecularMapStrengthf", (PROC)glSpecularMapStrengthf }, { "glGlassMaterialQD3D12", (PROC)glGlassMaterialQD3D12 }, { "glMaterialGlassQD3D12", (PROC)glMaterialGlassQD3D12 }, { "glRaytracingMaterialFlagsQD3D12", (PROC)glRaytracingMaterialFlagsQD3D12 }, { "glRaytracingMaterialFlagQD3D12", (PROC)glRaytracingMaterialFlagQD3D12 }, { "glResolveGBufferQD3D12", (PROC)glResolveGBufferQD3D12 }, { "QD3D12_ResolveGBufferNow", (PROC)QD3D12_ResolveGBufferNow }, { "QD3D12_SetUpscalerBackend", (PROC)QD3D12_SetUpscalerBackend }, { "QD3D12_SetUpscalerQuality", (PROC)QD3D12_SetUpscalerQuality }, { "QD3D12_SetUpscalerSharpness", (PROC)QD3D12_SetUpscalerSharpness }, { "QD3D12_SetToneMapBrightness", (PROC)QD3D12_SetToneMapBrightness }, { "QD3D12_GetToneMapBrightness", (PROC)QD3D12_GetToneMapBrightness }, { "glToneMapBrightnessQD3D12", (PROC)glToneMapBrightnessQD3D12 }, { "glToneMapBrightnessfQD3D12", (PROC)glToneMapBrightnessfQD3D12 }, { "glTonemapBrightnessQD3D12", (PROC)glTonemapBrightnessQD3D12 }, { "glTonemapBrightnessfQD3D12", (PROC)glTonemapBrightnessfQD3D12 }, { "QD3D12_EnableRayAIDenoise", (PROC)QD3D12_EnableRayAIDenoise }, { "QD3D12_EnableDLSSRayReconstruction", (PROC)QD3D12_EnableDLSSRayReconstruction }, { "QD3D12_EnableFSRRayRegeneration", (PROC)QD3D12_EnableFSRRayRegeneration }, { "QD3D12_EnableDLAA", (PROC)QD3D12_EnableDLAA }, { "QD3D12_IsDLAAEnabled", (PROC)QD3D12_IsDLAAEnabled }, { "glDLAAQD3D12", (PROC)glDLAAQD3D12 }, { "glEnableDLAAQD3D12", (PROC)glEnableDLAAQD3D12 }, { "QD3D12_EnableTAA", (PROC)QD3D12_EnableTAA }, { "QD3D12_IsTAAEnabled", (PROC)QD3D12_IsTAAEnabled }, { "glTAAQD3D12", (PROC)glTAAQD3D12 }, { "glEnableTAAQD3D12", (PROC)glEnableTAAQD3D12 }, { "glTemporalAAQD3D12", (PROC)glTemporalAAQD3D12 }, { "glEnableTemporalAAQD3D12", (PROC)glEnableTemporalAAQD3D12 }, { "QD3D12_SetPathTracingQuality", (PROC)QD3D12_SetPathTracingQuality }, { "QD3D12_SetPathTracingFallbackSamples", (PROC)QD3D12_SetPathTracingFallbackSamples }, { "glSetTopLevelAccelStructureVisible", (PROC)glSetTopLevelAccelStructureVisible }, { "glHideTopLevelAccelStructure", (PROC)glHideTopLevelAccelStructure }, { "glShowTopLevelAccelStructure", (PROC)glShowTopLevelAccelStructure }, { "glIsTopLevelAccelStructureVisible", (PROC)glIsTopLevelAccelStructureVisible }, { "glHideAllTopLevelAccelStructures", (PROC)glHideAllTopLevelAccelStructures }, { "glShowAllTopLevelAccelStructures", (PROC)glShowAllTopLevelAccelStructures }, { "glSetTopLevelAceelStructureVisible", (PROC)glSetTopLevelAceelStructureVisible }, { "glHideTopLevelAceelStructure", (PROC)glHideTopLevelAceelStructure }, { "glShowTopLevelAceelStructure", (PROC)glShowTopLevelAceelStructure }, { "glIsTopLevelAceelStructureVisible", (PROC)glIsTopLevelAceelStructureVisible }, { "glHideAllTopLevelAceelStructures", (PROC)glHideAllTopLevelAceelStructures }, { "glShowAllTopLevelAceelStructures", (PROC)glShowAllTopLevelAceelStructures }, { "glRaytracingLightingSetVolumetricScattering", (PROC)glRaytracingLightingSetVolumetricScattering }, { "glGenProgramsARB", (PROC)glGenProgramsARB }, { "glDeleteProgramsARB", (PROC)glDeleteProgramsARB }, { "glBindProgramARB", (PROC)glBindProgramARB }, { "glProgramStringARB", (PROC)glProgramStringARB }, { "glProgramEnvParameter4fARB", (PROC)glProgramEnvParameter4fARB }, { "glProgramEnvParameter4fvARB", (PROC)glProgramEnvParameter4fvARB }, { "glProgramLocalParameter4fARB", (PROC)glProgramLocalParameter4fARB }, { "glProgramLocalParameter4fvARB", (PROC)glProgramLocalParameter4fvARB }, { "glGetProgramEnvParameterfvARB", (PROC)glGetProgramEnvParameterfvARB }, { "glGetProgramLocalParameterfvARB", (PROC)glGetProgramLocalParameterfvARB }, { "glGetProgramivARB", (PROC)glGetProgramivARB }, { "glGetProgramStringARB", (PROC)glGetProgramStringARB }, { "glIsProgramARB", (PROC)glIsProgramARB }, { "glDepthBoundsEXT", (PROC)glDepthBoundsEXT }, { "glActiveStencilFaceEXT", (PROC)glActiveStencilFaceEXT }, { "glStencilOpSeparateATI", (PROC)glStencilOpSeparateATI }, { "glStencilFuncSeparateATI", (PROC)glStencilFuncSeparateATI }, { "glStencilMaskSeparate", (PROC)glStencilMaskSeparate }, { "glPolygonOffset", (PROC)glPolygonOffset }, { "wglSwapIntervalEXT", (PROC)qd3d12_wglSwapIntervalEXT }, { "wglGetDeviceGammaRamp3DFX", (PROC)qd3d12_wglGetDeviceGammaRamp3DFX }, { "wglSetDeviceGammaRamp3DFX", (PROC)qd3d12_wglSetDeviceGammaRamp3DFX }, { "glBindTextureEXT", (PROC)glBindTextureEXT }, { "glCompressedTexImage2DARB", (PROC)glCompressedTexImage2DARB }, { "glCompressedTexImage2DEXT", (PROC)glCompressedTexImage2DEXT }, { "glCompressedTexImage2D", (PROC)glCompressedTexImage2D }, { "glCompressedTexSubImage2DARB", (PROC)glCompressedTexSubImage2DARB }, { "glGetCompressedTexImageARB", (PROC)glGetCompressedTexImageARB }, { "glCompressedTexSubImage2DEXT", (PROC)glCompressedTexSubImage2DEXT }, { "glCompressedTexSubImage2D", (PROC)glCompressedTexSubImage2D }, { "glGetTexLevelParameteriv", (PROC)glGetTexLevelParameteriv }, { "glGetTexLevelParameterfv", (PROC)glGetTexLevelParameterfv }, { "wglGetExtensionsStringARB", (PROC)wglGetExtensionsStringARB }, { "wglGetExtensionsStringEXT", (PROC)wglGetExtensionsStringEXT }, { "wglChoosePixelFormatARB", (PROC)wglChoosePixelFormatARB }, { "wglGetPixelFormatAttribivARB", (PROC)wglGetPixelFormatAttribivARB }, { "wglCreatePbufferARB", (PROC)wglCreatePbufferARB }, { "wglGetPbufferDCARB", (PROC)wglGetPbufferDCARB }, { "wglReleasePbufferDCARB", (PROC)wglReleasePbufferDCARB }, { "wglDestroyPbufferARB", (PROC)wglDestroyPbufferARB }, { "wglQueryPbufferARB", (PROC)wglQueryPbufferARB }, { "wglSetPbufferAttribARB", (PROC)wglSetPbufferAttribARB }, { "wglBindTexImageARB", (PROC)wglBindTexImageARB }, { "wglReleaseTexImageARB", (PROC)wglReleaseTexImageARB }, }; for (size_t i = 0; i < _countof(table); ++i) { if (!strcmp(name, table[i].name)) return table[i].proc; } return nullptr; } void APIENTRY glFogf(GLenum pname, GLfloat param) { switch (pname) { case GL_FOG_MODE: g_gl.fogMode = (GLenum)param; break; case GL_FOG_DENSITY: g_gl.fogDensity = param; break; case GL_FOG_START: g_gl.fogStart = param; break; case GL_FOG_END: g_gl.fogEnd = param; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glFogi(GLenum pname, GLint param) { glFogf(pname, (GLfloat)param); } void APIENTRY glFogfv(GLenum pname, const GLfloat* params) { if (!params) return; switch (pname) { case GL_FOG_MODE: g_gl.fogMode = (GLenum)params[0]; break; case GL_FOG_DENSITY: g_gl.fogDensity = params[0]; break; case GL_FOG_START: g_gl.fogStart = params[0]; break; case GL_FOG_END: g_gl.fogEnd = params[0]; break; case GL_FOG_COLOR: g_gl.fogColor[0] = params[0]; g_gl.fogColor[1] = params[1]; g_gl.fogColor[2] = params[2]; g_gl.fogColor[3] = params[3]; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glFogiv(GLenum pname, const GLint* params) { if (!params) return; switch (pname) { case GL_FOG_MODE: g_gl.fogMode = (GLenum)params[0]; break; case GL_FOG_DENSITY: g_gl.fogDensity = (GLfloat)params[0]; break; case GL_FOG_START: g_gl.fogStart = (GLfloat)params[0]; break; case GL_FOG_END: g_gl.fogEnd = (GLfloat)params[0]; break; case GL_FOG_COLOR: g_gl.fogColor[0] = (GLfloat)params[0]; g_gl.fogColor[1] = (GLfloat)params[1]; g_gl.fogColor[2] = (GLfloat)params[2]; g_gl.fogColor[3] = (GLfloat)params[3]; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } ID3D12Device* QD3D12_GetDevice(void) { return g_gl.device.Get(); } ID3D12CommandQueue* QD3D12_GetQueue(void) { return g_gl.queue.Get(); } ID3D12GraphicsCommandList* QD3D12_GetCommandList(void) { return g_gl.cmdList.Get(); } ID3D12CommandAllocator* QD3D12_GetFrameCommandAllocator(void) { return g_currentWindow->frames[g_currentWindow->frameIndex].cmdAlloc.Get(); } UINT QD3D12_GetFrameIndex(void) { return g_currentWindow->frameIndex; } void QD3D12_WaitForGPU_External(void) { if (g_gl.frameOpen || !g_gl.queuedBatches.empty()) QD3D12_SubmitOpenFrameNoPresentAndWait(); QD3D12_WaitForGPU(); } ID3D12Resource* glRaytracingGetTopLevelAS(void); static void QD3D12_TransitionResource( ID3D12GraphicsCommandList* cl, ID3D12Resource* res, D3D12_RESOURCE_STATES& trackedState, D3D12_RESOURCE_STATES newState) { if (!res || trackedState == newState) return; D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = res; b.Transition.StateBefore = trackedState; b.Transition.StateAfter = newState; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; cl->ResourceBarrier(1, &b); trackedState = newState; } static void QD3D12_ResolveMsaaDepthToSceneDepth(QD3D12Window& w) { if (!QD3D12_GBufferMsaaEnabled()) return; ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !w.depthMsaaBuffer || !w.depthBuffer || !g_gl.postDepthResolveMsaaPSO) return; QD3D12_TransitionResource(cl, w.depthMsaaBuffer.Get(), w.depthMsaaState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.depthBuffer.Get(), w.depthState, D3D12_RESOURCE_STATE_DEPTH_WRITE); D3D12_CPU_DESCRIPTOR_HANDLE dsv = CurrentSceneDepthDSV(); 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; cl->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postDepthResolveMsaaPSO.Get()); cl->OMSetRenderTargets(0, nullptr, FALSE, &dsv); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cl->SetGraphicsRootDescriptorTable(1, w.depthMsaaSrvGpu); cl->DrawInstanced(3, 1, 0, 0); } static void QD3D12_PointResolveMsaaGBufferToSingleSample(QD3D12Window& w) { if (!QD3D12_GBufferMsaaEnabled()) return; ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !g_gl.postGBufferPointResolveMsaaPSO) return; const UINT frame = w.frameIndex; if (!w.depthMsaaBuffer || !w.normalMsaaBuffers[frame] || !w.positionMsaaBuffers[frame] || !w.velocityMsaaBuffers[frame] || !w.emissiveMsaaBuffers[frame] || !w.specularMsaaBuffers[frame] || !w.normalBuffers[frame] || !w.positionBuffers[frame] || !w.velocityBuffers[frame] || !w.emissiveBuffers[frame] || !w.specularBuffers[frame]) { return; } QD3D12_TransitionResource(cl, w.depthMsaaBuffer.Get(), w.depthMsaaState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); 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.specularMsaaBuffers[frame].Get(), w.specularMsaaState[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); QD3D12_TransitionResource(cl, w.specularBuffers[frame].Get(), w.specularBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_CPU_DESCRIPTOR_HANDLE rtvs[5] = { CurrentResolvedNormalRTV(), CurrentResolvedPositionRTV(), CurrentResolvedVelocityRTV(), CurrentResolvedEmissiveRTV(), CurrentResolvedSpecularRTV() }; 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; ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postGBufferPointResolveMsaaPSO.Get()); cl->OMSetRenderTargets(5, rtvs, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // t1..t6: 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->SetGraphicsRootDescriptorTable(6, w.specularMsaaSrvGpu[frame]); cl->DrawInstanced(3, 1, 0, 0); } static void QD3D12_ResolveGBufferForCurrentFrame(QD3D12Window& w) { if (g_gl.gbufferResolvedThisFrame) return; ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; if (!QD3D12_GBufferMsaaEnabled()) { g_gl.gbufferResolvedThisFrame = true; return; } const UINT frame = w.frameIndex; auto ResolveTarget = [&](ID3D12Resource* src, D3D12_RESOURCE_STATES& srcState, ID3D12Resource* dst, D3D12_RESOURCE_STATES& dstState, DXGI_FORMAT format) { if (!src || !dst) return; QD3D12_TransitionResource(cl, src, srcState, D3D12_RESOURCE_STATE_RESOLVE_SOURCE); QD3D12_TransitionResource(cl, dst, dstState, D3D12_RESOURCE_STATE_RESOLVE_DEST); cl->ResolveSubresource(dst, 0, src, 0, format); }; ResolveTarget( w.sceneColorMsaaBuffers[frame].Get(), w.sceneColorMsaaState[frame], w.sceneColorBuffers[frame].Get(), w.sceneColorState[frame], QD3D12_SceneColorFormat); // Do not hardware-resolve discontinuous deferred attributes. Hardware resolve // averages samples, which corrupts normals, world positions/material flags, // and motion vectors at geometry edges. Convert those buffers by point-loading // one MSAA sample selected from the nearest depth sample instead. QD3D12_PointResolveMsaaGBufferToSingleSample(w); QD3D12_ResolveMsaaDepthToSceneDepth(w); g_gl.gbufferResolvedThisFrame = true; } void APIENTRY QD3D12_ResolveGBufferNow(void) { if (!g_currentWindow || !g_gl.device || !g_gl.cmdList) return; QD3D12_EnsureFrameOpen(); QD3D12_FlushQueuedBatches(); QD3D12_ResolveGBufferForCurrentFrame(*g_currentWindow); } void APIENTRY glResolveGBufferQD3D12(void) { QD3D12_ResolveGBufferNow(); } static void QD3D12_BindLowResSceneTargets(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; if (QD3D12_GBufferMsaaEnabled()) { QD3D12_TransitionResource(cl, w.sceneColorMsaaBuffers[w.frameIndex].Get(), w.sceneColorMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); 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.specularMsaaBuffers[w.frameIndex].Get(), w.specularMsaaState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.depthMsaaBuffer.Get(), w.depthMsaaState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } else { QD3D12_TransitionResource(cl, w.sceneColorBuffers[w.frameIndex].Get(), w.sceneColorState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); 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.specularBuffers[w.frameIndex].Get(), w.specularBufferState[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[6] = { CurrentRTV(), CurrentNormalRTV(), CurrentPositionRTV(), CurrentVelocityRTV(), CurrentEmissiveRTV(), CurrentSpecularRTV() }; D3D12_CPU_DESCRIPTOR_HANDLE dsv = CurrentActiveSceneDepthDSV(); cl->OMSetRenderTargets(6, rtvs, FALSE, &dsv); cl->RSSetViewports(1, &w.viewport); cl->RSSetScissorRects(1, &w.scissor); } static void QD3D12_CopySceneDepthToNative(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !w.nativeDepthBuffer) return; QD3D12_TransitionResource(cl, w.depthBuffer.Get(), w.depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.nativeDepthBuffer.Get(), w.nativeDepthState, D3D12_RESOURCE_STATE_DEPTH_WRITE); D3D12_CPU_DESCRIPTOR_HANDLE dsv = CurrentNativeDepthDSV(); D3D12_VIEWPORT viewport{}; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; viewport.Width = (float)w.width; viewport.Height = (float)w.height; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; D3D12_RECT scissor{}; scissor.left = 0; scissor.top = 0; scissor.right = (LONG)w.width; scissor.bottom = (LONG)w.height; cl->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); if (!g_gl.postDepthCopyPSO) return; ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postDepthCopyPSO.Get()); cl->OMSetRenderTargets(0, nullptr, FALSE, &dsv); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cl->SetGraphicsRootDescriptorTable(0, w.depthSrvGpu); cl->DrawInstanced(3, 1, 0, 0); } static void QD3D12_BindNativePostUpscaleTargets(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; QD3D12_TransitionResource(cl, w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_CPU_DESCRIPTOR_HANDLE rtv = CurrentBackBufferRTV(); if (w.nativeDepthBuffer) { QD3D12_TransitionResource(cl, w.nativeDepthBuffer.Get(), w.nativeDepthState, D3D12_RESOURCE_STATE_DEPTH_WRITE); D3D12_CPU_DESCRIPTOR_HANDLE dsv = CurrentNativeDepthDSV(); cl->OMSetRenderTargets(1, &rtv, FALSE, &dsv); } else { cl->OMSetRenderTargets(1, &rtv, FALSE, nullptr); } cl->RSSetViewports(1, &w.viewport); cl->RSSetScissorRects(1, &w.scissor); } static void QD3D12_BindTargetsForCurrentPhase(QD3D12Window& w) { if (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE) QD3D12_BindNativePostUpscaleTargets(w); else QD3D12_BindLowResSceneTargets(w); } static void QD3D12_ResolveSceneToOutputAndEnterNativePhase(QD3D12Window& w) { if (g_gl.sceneResolvedThisFrame) return; ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; 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.positionBuffers[w.frameIndex].Get(), w.positionBufferState[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); QD3D12_TransitionResource(cl, w.specularBuffers[w.frameIndex].Get(), w.specularBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource(cl, w.depthBuffer.Get(), w.depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); if (useLightingUpscaleInput) { QD3D12_TransitionResource(cl, w.normalBuffers[w.frameIndex].Get(), w.normalBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); if (g_lightingTexture && g_lightingTexture->texture) { QD3D12_TransitionResource(cl, g_lightingTexture->texture.Get(), g_lightingTextureState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); } } QD3D12_TransitionResource(cl, w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_RunUpscalerOrBlit(w); QD3D12_CopySceneDepthToNative(w); g_gl.sceneResolvedThisFrame = true; g_gl.framePhase = QD3D12_FRAME_NATIVE_POST_UPSCALE; QD3D12_UpdateViewportState(); QD3D12_BindTargetsForCurrentPhase(w); } void glLightScene(glRaytracingSceneHandle_t sceneHandle) { if (sceneHandle == 0) return; auto* window = g_currentWindow; if (!window) return; const int width = (int)window->renderWidth; const int height = (int)window->renderHeight; const auto frameIndex = window->frameIndex; if (width <= 0 || height <= 0) return; if (!g_gl.device || !g_gl.cmdList) return; QD3D12_EnsureFrameOpen(); TextureResource* lightingTex = QD3D12_EnsureLightingTexture(width, height); if (!lightingTex || !lightingTex->texture) return; // Scene/TLAS ownership is now per render world. Querying the scene TLAS here // also builds dirty shared BLAS resources and this scene's TLAS before we // touch the window G-buffer state. ID3D12Resource* tlas = glRaytracingGetTopLevelASForScene(sceneHandle); if (!tlas) return; glRaytracingBuildSceneForHandle(sceneHandle); QD3D12_FlushQueuedBatches(); QD3D12_ResolveGBufferForCurrentFrame(*window); ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); 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* sceneSpecular = window->specularBuffers[frameIndex].Get(); ID3D12Resource* sceneDepth = window->depthBuffer.Get(); if (!sceneColor || !sceneNormal || !scenePosition || !sceneEmissive || !sceneSpecular || !sceneDepth) return; QD3D12_TransitionResource( cl, sceneColor, window->sceneColorState[frameIndex], D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource( cl, sceneNormal, window->normalBufferState[frameIndex], D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource( cl, scenePosition, 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, sceneSpecular, window->specularBufferState[frameIndex], D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); QD3D12_TransitionResource( cl, sceneDepth, window->depthState, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); // The external DXR module records and executes its own command list. Because // of that, the scene input transitions must be submitted on the main command // list before we call into glRaytracingLightingExecuteForScene. if (g_lightingTextureState != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) { QD3D12_TransitionResource( cl, lightingTex->texture.Get(), g_lightingTextureState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); g_lightingTextureState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; } QD3D12_ExecuteMainCommandListAndWait(*window); cl = g_gl.cmdList.Get(); QD3D12_UpdateCameraInfoFromCurrentMatrices(); const bool useDLSSRayReconstruction = QD3D12_CanUseDLSSRayReconstructionForLighting(*window); const uint32_t activeMaxBounces = std::max(1u, g_gl.pathTracingMaxBounces); const uint32_t raySpp = useDLSSRayReconstruction ? std::max(1u, g_gl.pathTracingSamplesPerPixel) : std::max(1u, g_gl.pathTracingFallbackSamplesPerPixel); // Keep Streamline/DLSS RR on the raw lighting path. The noise fix is now in // the DXR sampling itself: stable GI seeds, deterministic area-light samples, // and non-jittered volume slices. Feeding RR a prefiltered image can make the // temporal reconstruction amplify blur/speckles. glRaytracingLightingSetExternalDenoiser(useDLSSRayReconstruction ? 1 : 0); glRaytracingLightingSetPathTracingOptions( raySpp, activeMaxBounces, useDLSSRayReconstruction ? 0 : 1, useDLSSRayReconstruction ? 0.0f : 0.75f); glRaytracingLightingSetEmissiveInput(sceneEmissive, QD3D12_EmissiveFormat); glRaytracingLightingSetSpecularInput(sceneSpecular, QD3D12_SpecularAlbedoFormat); glRaytracingLightingPassDesc_t pass = {}; pass.albedoTexture = sceneColor; pass.albedoFormat = QD3D12_SceneColorFormat; pass.normalTexture = sceneNormal; pass.normalFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; pass.positionTexture = scenePosition; pass.positionFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; pass.depthTexture = sceneDepth; pass.depthFormat = QD3D12_DepthSrvFormat; pass.outputTexture = lightingTex->texture.Get(); pass.outputFormat = lightingTex->dxgiFormat; pass.width = (uint32_t)width; pass.height = (uint32_t)height; if (!glRaytracingLightingExecuteForScene(&pass, sceneHandle)) { if (QD3D12_GBufferMsaaEnabled()) { QD3D12_TransitionResource(cl, window->sceneColorMsaaBuffers[frameIndex].Get(), window->sceneColorMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); 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->specularMsaaBuffers[frameIndex].Get(), window->specularMsaaState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, window->depthMsaaBuffer.Get(), window->depthMsaaState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } else { QD3D12_TransitionResource( cl, sceneColor, window->sceneColorState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource( cl, sceneNormal, window->normalBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource( cl, scenePosition, window->positionBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource( cl, sceneEmissive, window->emissiveBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource( cl, sceneSpecular, window->specularBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource( cl, sceneDepth, window->depthState, D3D12_RESOURCE_STATE_DEPTH_WRITE); } g_gl.gbufferResolvedThisFrame = false; return; } // The scene-handle DXR path leaves outputTexture in PIXEL_SHADER_RESOURCE. g_lightingTextureState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; QD3D12_RunRayAIDenoiseIfEnabled(cl, *window, lightingTex->texture.Get()); // If the denoiser changes the texture state and updates g_lightingTextureState, // restore the texture to a shader-readable state for the post/upscale pass. if (g_lightingTextureState != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) { QD3D12_TransitionResource( cl, lightingTex->texture.Get(), g_lightingTextureState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); g_lightingTextureState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; } g_gl.raytracedLightingReadyThisFrame = true; if (!QD3D12_UseLightingTextureAsUpscaleInput(*window)) { QD3D12_TransitionResource( cl, sceneColor, window->sceneColorState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_VIEWPORT viewport{}; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; viewport.Width = (float)window->renderWidth; viewport.Height = (float)window->renderHeight; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; D3D12_RECT scissor{}; scissor.left = 0; scissor.top = 0; scissor.right = (LONG)window->renderWidth; scissor.bottom = (LONG)window->renderHeight; // Non-RR path: replace the low-resolution scene color with the lit result // after measuring the brightest HDR pixel and using it as the tone-map // white point. Keep sharpness at zero here; final output sharpening stays // in QD3D12_FinalBlitToBackBuffer(). if (!QD3D12_ToneMapFullscreenPass( *window, lightingTex->srvGpu, CurrentResolvedSceneColorRTV(), viewport, scissor, 0.0f, false)) { QD3D12_PostFullscreenPass( cl, g_gl.postCopyPSO.Get(), lightingTex->srvGpu, CurrentResolvedSceneColorRTV(), viewport, scissor, 0.0f); } } else { // DLSS Ray Reconstruction consumes the noisy ray-traced lighting texture // directly as the scaling input, so keep sceneColor intact as the albedo // guide instead of overwriting it here. } QD3D12_ResolveSceneToOutputAndEnterNativePhase(*window); } ID3D12Resource* QD3D12_GetCurrentBackBuffer() { if (!g_currentWindow->swapChain) return nullptr; const UINT index = g_currentWindow->frameIndex; if (index >= QD3D12_FrameCount) return nullptr; return g_currentWindow->backBuffers[index].Get(); } void APIENTRY glGeometryFlagf(GLfloat flag) { g_gl.currentGeometryFlag = flag; } void APIENTRY glMotionObjectIdui(GLuint objectId) { g_gl.currentMotionObjectId = objectId; } void APIENTRY glSurfaceRoughnessf(GLfloat roughness) { g_gl.currentSurfaceRoughness = ClampValue(roughness, 0.0f, 1.0f); } void APIENTRY glMaterialTypef(GLfloat materialType) { g_gl.currentMaterialType = materialType; } void QD3D12_SetUpscalerBackend(int backend) { switch (backend) { case QD3D12_UPSCALER_DLSS: g_gl.upscalerBackend = QD3D12_UPSCALER_DLSS; break; case QD3D12_UPSCALER_FSR: g_gl.upscalerBackend = QD3D12_UPSCALER_FSR; break; default: g_gl.upscalerBackend = QD3D12_UPSCALER_NONE; break; } // DLAA is not a generic native-quality mode; it is a DLSS mode. If the // backend is changed away from DLSS, fall back to ordinary native rendering. if (g_gl.upscalerBackend != QD3D12_UPSCALER_DLSS && QD3D12_IsDLAAQuality(g_gl.upscalerQuality)) { g_gl.upscalerQuality = QD3D12_QUALITY_NATIVE; } g_gl.motionHistoryReset = true; QD3D12_ReconfigureCurrentWindowForUpscalerChange(); } void QD3D12_SetUpscalerQuality(int quality) { switch (quality) { case QD3D12_QUALITY_NATIVE: case QD3D12_QUALITY_QUALITY: case QD3D12_QUALITY_BALANCED: case QD3D12_QUALITY_PERFORMANCE: case QD3D12_QUALITY_ULTRA_PERFORMANCE: g_gl.upscalerQuality = (QD3D12UpscalerQuality)quality; break; case QD3D12_QUALITY_DLAA: g_gl.upscalerBackend = QD3D12_UPSCALER_DLSS; g_gl.upscalerQuality = QD3D12_QUALITY_DLAA; break; default: g_gl.upscalerQuality = QD3D12_QUALITY_QUALITY; break; } g_gl.motionHistoryReset = true; QD3D12_ReconfigureCurrentWindowForUpscalerChange(); } void QD3D12_EnableDLAA(int enabled) { const QD3D12UpscalerBackend oldBackend = g_gl.upscalerBackend; const QD3D12UpscalerQuality oldQuality = g_gl.upscalerQuality; if (enabled) { // DLAA is DLSS-only. Force the DLSS backend so callers can enable it with // one function call even if a previous menu selection left FSR or no // upscaler active. g_gl.upscalerBackend = QD3D12_UPSCALER_DLSS; g_gl.upscalerQuality = QD3D12_QUALITY_DLAA; } else if (g_gl.upscalerQuality == QD3D12_QUALITY_DLAA) { g_gl.upscalerQuality = QD3D12_QUALITY_NATIVE; } if (oldBackend != g_gl.upscalerBackend || oldQuality != g_gl.upscalerQuality) { g_gl.motionHistoryReset = true; QD3D12_ReconfigureCurrentWindowForUpscalerChange(); } } int QD3D12_IsDLAAEnabled(void) { return (g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_gl.upscalerQuality == QD3D12_QUALITY_DLAA) ? 1 : 0; } void APIENTRY glDLAAQD3D12(GLboolean enable) { QD3D12_EnableDLAA(enable != GL_FALSE ? 1 : 0); } void APIENTRY glEnableDLAAQD3D12(GLboolean enable) { glDLAAQD3D12(enable); } static void QD3D12_ResetInternalTAAHistoryForWindow(QD3D12Window* window) { if (!window) return; for (UINT i = 0; i < QD3D12_FrameCount; ++i) window->taaHistoryValid[i] = false; } void QD3D12_EnableTAA(int enabled) { const bool newValue = enabled ? true : false; if (g_gl.enableInternalTAA == newValue) return; g_gl.enableInternalTAA = newValue; g_gl.motionHistoryReset = true; // TAA history is only valid for the previous TAA mode. Drop it so re-enabling // starts from the current frame instead of blending against stale history. QD3D12_ResetInternalTAAHistoryForWindow(g_currentWindow); for (auto& kv : g_windows) QD3D12_ResetInternalTAAHistoryForWindow(&kv.second); } int QD3D12_IsTAAEnabled(void) { return g_gl.enableInternalTAA ? 1 : 0; } void APIENTRY glTAAQD3D12(GLboolean enable) { QD3D12_EnableTAA(enable != GL_FALSE ? 1 : 0); } void APIENTRY glEnableTAAQD3D12(GLboolean enable) { glTAAQD3D12(enable); } void APIENTRY glTemporalAAQD3D12(GLboolean enable) { glTAAQD3D12(enable); } void APIENTRY glEnableTemporalAAQD3D12(GLboolean enable) { glTAAQD3D12(enable); } void QD3D12_SetUpscalerSharpness(float sharpness) { g_gl.upscalerSharpness = ClampValue(sharpness, 0.0f, 1.0f); } static float QD3D12_ClampToneMapBrightness(float brightness) { if (!std::isfinite(brightness)) return 1.0f; return ClampValue(brightness, 0.0f, 8.0f); } void QD3D12_SetToneMapBrightness(float brightness) { g_gl.toneMapBrightness = QD3D12_ClampToneMapBrightness(brightness); } float QD3D12_GetToneMapBrightness(void) { return g_gl.toneMapBrightness; } void APIENTRY glToneMapBrightnessQD3D12(GLfloat brightness) { QD3D12_SetToneMapBrightness(brightness); } void APIENTRY glToneMapBrightnessfQD3D12(GLfloat brightness) { QD3D12_SetToneMapBrightness(brightness); } void APIENTRY glTonemapBrightnessQD3D12(GLfloat brightness) { QD3D12_SetToneMapBrightness(brightness); } void APIENTRY glTonemapBrightnessfQD3D12(GLfloat brightness) { QD3D12_SetToneMapBrightness(brightness); } void QD3D12_EnableRayAIDenoise(int enabled) { g_gl.enableRayAIDenoise = enabled ? true : false; g_gl.motionHistoryReset = true; } void QD3D12_EnableDLSSRayReconstruction(int enabled) { const bool newValue = enabled ? true : false; if (g_gl.enableDLSSRayReconstruction == newValue) return; g_gl.enableDLSSRayReconstruction = newValue; g_gl.enableRayAIDenoise = false; g_gl.motionHistoryReset = true; QD3D12_ReconfigureCurrentWindowForUpscalerChange(); } void QD3D12_EnableFSRRayRegeneration(int enabled) { g_gl.enableFSRRayRegeneration = enabled ? true : false; } void QD3D12_SetPathTracingQuality(uint32_t samplesPerPixel, uint32_t maxBounces) { g_gl.pathTracingSamplesPerPixel = std::max(1u, std::min(samplesPerPixel ? samplesPerPixel : 1u, 8u)); g_gl.pathTracingFallbackSamplesPerPixel = std::max(g_gl.pathTracingSamplesPerPixel, 2u); g_gl.pathTracingMaxBounces = std::max(1u, std::min(maxBounces ? maxBounces : 1u, 4u)); g_gl.motionHistoryReset = true; } void QD3D12_SetPathTracingFallbackSamples(uint32_t samplesPerPixel) { g_gl.pathTracingFallbackSamplesPerPixel = std::max(1u, std::min(samplesPerPixel ? samplesPerPixel : 1u, 8u)); g_gl.motionHistoryReset = true; } void QD3D12_ResetTemporalHistory(void) { g_gl.motionHistoryReset = true; g_gl.prevObjectMVPs.clear(); g_gl.currObjectMVPs.clear(); g_gl.prevJitterX = 0.0f; g_gl.prevJitterY = 0.0f; QD3D12_ResetAutoCameraHistory(); } void QD3D12_SetProjectionJitterPixels(float jitterX, float jitterY) { g_gl.jitterX = jitterX; g_gl.jitterY = jitterY; } void QD3D12_SetCameraInfo( const float* viewToClip, const float* clipToView, const float* clipToPrevClip, const float* prevClipToClip, const float* cameraPos, const float* cameraRight, const float* cameraUp, const float* cameraForward, float nearPlane, float farPlane, float verticalFovRadians, float aspectRatio) { if (viewToClip) memcpy(g_gl.cameraState.viewToClip.m, viewToClip, sizeof(float) * 16); if (clipToView) memcpy(g_gl.cameraState.clipToView.m, clipToView, sizeof(float) * 16); if (clipToPrevClip) memcpy(g_gl.cameraState.clipToPrevClip.m, clipToPrevClip, sizeof(float) * 16); if (prevClipToClip) memcpy(g_gl.cameraState.prevClipToClip.m, prevClipToClip, sizeof(float) * 16); if (cameraPos) { g_gl.cameraState.cameraPos[0] = cameraPos[0]; g_gl.cameraState.cameraPos[1] = cameraPos[1]; g_gl.cameraState.cameraPos[2] = cameraPos[2]; } if (cameraRight) { g_gl.cameraState.cameraRight[0] = cameraRight[0]; g_gl.cameraState.cameraRight[1] = cameraRight[1]; g_gl.cameraState.cameraRight[2] = cameraRight[2]; } if (cameraUp) { g_gl.cameraState.cameraUp[0] = cameraUp[0]; g_gl.cameraState.cameraUp[1] = cameraUp[1]; g_gl.cameraState.cameraUp[2] = cameraUp[2]; } if (cameraForward) { g_gl.cameraState.cameraForward[0] = cameraForward[0]; g_gl.cameraState.cameraForward[1] = cameraForward[1]; g_gl.cameraState.cameraForward[2] = cameraForward[2]; } g_gl.cameraState.nearPlane = nearPlane; g_gl.cameraState.farPlane = farPlane; g_gl.cameraState.verticalFovRadians = verticalFovRadians; g_gl.cameraState.aspectRatio = aspectRatio; g_gl.cameraState.valid = true; } void APIENTRY glGenBuffers(GLsizei n, GLuint* buffers) { if (n < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (!buffers) return; for (GLsizei i = 0; i < n; ++i) { GLuint id = g_gl.nextBufferId++; GLBufferObject bo{}; bo.id = id; g_gl.buffers.emplace(id, std::move(bo)); buffers[i] = id; } } void APIENTRY glDeleteBuffers(GLsizei n, const GLuint* buffers) { if (n < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (!buffers) return; for (GLsizei i = 0; i < n; ++i) { const GLuint id = buffers[i]; if (id == 0) continue; if (g_gl.boundArrayBuffer == id) g_gl.boundArrayBuffer = 0; if (g_gl.boundElementArrayBuffer == id) g_gl.boundElementArrayBuffer = 0; g_gl.buffers.erase(id); } } GLboolean APIENTRY glIsBuffer(GLuint buffer) { return g_gl.buffers.find(buffer) != g_gl.buffers.end() ? GL_TRUE : GL_FALSE; } void APIENTRY glBindBuffer(GLenum target, GLuint buffer) { switch (target) { case GL_ARRAY_BUFFER: g_gl.boundArrayBuffer = buffer; break; case GL_ELEMENT_ARRAY_BUFFER: g_gl.boundElementArrayBuffer = buffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return; } if (buffer == 0) return; auto it = g_gl.buffers.find(buffer); if (it == g_gl.buffers.end()) { GLBufferObject bo{}; bo.id = buffer; bo.target = target; g_gl.buffers.emplace(buffer, std::move(bo)); } else { it->second.target = target; } } void APIENTRY glBufferStorage(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) { GLuint bound = 0; switch (target) { case GL_ARRAY_BUFFER: bound = g_gl.boundArrayBuffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound = g_gl.boundElementArrayBuffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return; } if (size < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (bound == 0) { g_gl.lastError = GL_INVALID_OPERATION; return; } GLBufferObject* bo = QD3D12_GetBuffer(bound); if (!bo) { g_gl.lastError = GL_INVALID_OPERATION; return; } bo->target = target; bo->storageFlags = flags; bo->data.resize((size_t)size); if (size > 0) { if (data) memcpy(bo->data.data(), data, (size_t)size); else memset(bo->data.data(), 0, (size_t)size); } } void APIENTRY glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { (void)usage; glBufferStorage(target, size, data, 0); } void APIENTRY glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data) { GLuint bound = 0; switch (target) { case GL_ARRAY_BUFFER: bound = g_gl.boundArrayBuffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound = g_gl.boundElementArrayBuffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return; } if (offset < 0 || size < 0 || !data) { g_gl.lastError = GL_INVALID_VALUE; return; } GLBufferObject* bo = QD3D12_GetBuffer(bound); if (!bo) { g_gl.lastError = GL_INVALID_OPERATION; return; } if ((size_t)offset > bo->data.size() || (size_t)size > (bo->data.size() - (size_t)offset)) { g_gl.lastError = GL_INVALID_VALUE; return; } memcpy(bo->data.data() + (size_t)offset, data, (size_t)size); } void APIENTRY glDrawArrays(GLenum mode, GLint first, GLsizei count) { if (count <= 0) return; if (first < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.immediateVerts.Clear(); for (GLsizei i = 0; i < count; ++i) { GLVertex& v = g_gl.immediateVerts.Push(); QD3D12_FetchArrayVertex(first + i, v); } FlushImmediate(mode, g_gl.immediateVerts.Data(), g_gl.immediateVerts.Size()); } void* APIENTRY glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) { GLuint bound = 0; switch (target) { case GL_ARRAY_BUFFER: bound = g_gl.boundArrayBuffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound = g_gl.boundElementArrayBuffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return nullptr; } if (offset < 0 || length < 0) { g_gl.lastError = GL_INVALID_VALUE; return nullptr; } GLBufferObject* bo = QD3D12_GetBuffer(bound); if (!bo) { g_gl.lastError = GL_INVALID_OPERATION; return nullptr; } if (bo->mapped) { g_gl.lastError = GL_INVALID_OPERATION; return nullptr; } if ((size_t)offset > bo->data.size() || (size_t)length > (bo->data.size() - (size_t)offset)) { g_gl.lastError = GL_INVALID_VALUE; return nullptr; } // optional light validation if ((access & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)) == 0) { g_gl.lastError = GL_INVALID_OPERATION; return nullptr; } // emulate invalidation/orphan-ish behavior on CPU backing store #ifdef GL_MAP_INVALIDATE_RANGE_BIT if ((access & GL_MAP_INVALIDATE_RANGE_BIT) && length > 0) { memset(bo->data.data() + offset, 0, (size_t)length); } #endif #ifdef GL_MAP_INVALIDATE_BUFFER_BIT if ((access & GL_MAP_INVALIDATE_BUFFER_BIT) && !bo->data.empty()) { memset(bo->data.data(), 0, bo->data.size()); } #endif bo->mapped = true; bo->mappedOffset = offset; bo->mappedLength = length; bo->mappedAccess = access; return bo->data.data() + offset; } GLboolean APIENTRY glUnmapBuffer(GLenum target) { GLuint bound = 0; switch (target) { case GL_ARRAY_BUFFER: bound = g_gl.boundArrayBuffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound = g_gl.boundElementArrayBuffer; break; default: g_gl.lastError = GL_INVALID_ENUM; return GL_FALSE; } GLBufferObject* bo = QD3D12_GetBuffer(bound); if (!bo) { g_gl.lastError = GL_INVALID_OPERATION; return GL_FALSE; } if (!bo->mapped) { g_gl.lastError = GL_INVALID_OPERATION; return GL_FALSE; } bo->mapped = false; bo->mappedOffset = 0; bo->mappedLength = 0; bo->mappedAccess = 0; return GL_TRUE; } void APIENTRY glGenQueries(GLsizei n, GLuint* ids) { if (n < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (!ids) return; for (GLsizei i = 0; i < n; ++i) { GLuint id = g_gl.nextQueryId++; GLOcclusionQuery q{}; q.id = id; q.heapIndex = id % QD3D12_MaxQueries; // simple scheme; good enough if IDs stay bounded g_gl.queries.emplace(id, q); ids[i] = id; } } void APIENTRY glDeleteQueries(GLsizei n, const GLuint* ids) { if (n < 0) { g_gl.lastError = GL_INVALID_VALUE; return; } if (!ids) return; for (GLsizei i = 0; i < n; ++i) { GLuint id = ids[i]; if (id == 0) continue; if (g_gl.currentQuery == id) { g_gl.lastError = GL_INVALID_OPERATION; return; } g_gl.queries.erase(id); } } GLboolean APIENTRY glIsQuery(GLuint id) { return g_gl.queries.find(id) != g_gl.queries.end() ? GL_TRUE : GL_FALSE; } void APIENTRY glBeginQuery(GLenum target, GLuint id) { if (target != GL_SAMPLES_PASSED) { g_gl.lastError = GL_INVALID_ENUM; return; } auto it = g_gl.queries.find(id); if (it == g_gl.queries.end() || id == 0) { g_gl.lastError = GL_INVALID_OPERATION; return; } if (g_gl.currentQuery != 0) { g_gl.lastError = GL_INVALID_OPERATION; return; } GLOcclusionQuery& q = it->second; q.active = true; q.pending = false; q.resultReady = false; q.result = 0; g_gl.currentQuery = id; QueryMarker m{}; m.type = QueryMarker::Begin; m.id = id; g_gl.queryMarkers.push_back(m); if (!g_gl.queuedBatches.empty()) g_gl.queuedBatches.back().markerEnd = g_gl.queryMarkers.size(); } void APIENTRY glEndQuery(GLenum target) { if (target != GL_SAMPLES_PASSED) { g_gl.lastError = GL_INVALID_ENUM; return; } if (g_gl.currentQuery == 0) { g_gl.lastError = GL_INVALID_OPERATION; return; } auto it = g_gl.queries.find(g_gl.currentQuery); if (it == g_gl.queries.end()) { g_gl.lastError = GL_INVALID_OPERATION; g_gl.currentQuery = 0; return; } it->second.active = false; it->second.pending = true; QueryMarker m{}; m.type = QueryMarker::End; m.id = g_gl.currentQuery; g_gl.queryMarkers.push_back(m); g_gl.currentQuery = 0; if (!g_gl.queuedBatches.empty()) g_gl.queuedBatches.back().markerEnd = g_gl.queryMarkers.size(); } static void QD3D12_UpdateQueryResult(GLOcclusionQuery& q) { if (!q.pending || q.resultReady) return; if (q.submittedFence == 0) return; if (g_gl.fence->GetCompletedValue() < q.submittedFence) return; q.result = g_gl.occlusionReadbackCpu[q.heapIndex]; q.resultReady = true; q.pending = false; } void APIENTRY glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) { if (!params) return; auto it = g_gl.queries.find(id); if (it == g_gl.queries.end()) { g_gl.lastError = GL_INVALID_OPERATION; return; } GLOcclusionQuery& q = it->second; QD3D12_UpdateQueryResult(q); switch (pname) { case GL_QUERY_RESULT_AVAILABLE: *params = q.resultReady ? GL_TRUE : GL_FALSE; break; case GL_QUERY_RESULT: if (!q.resultReady) { QD3D12_WaitForGPU(); QD3D12_UpdateQueryResult(q); } *params = (GLuint)q.result; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glGetQueryObjectiv(GLuint id, GLenum pname, GLint* params) { if (!params) return; GLuint u = 0; glGetQueryObjectuiv(id, pname, &u); *params = (GLint)u; } void APIENTRY glPointSize(GLfloat size) { if (size <= 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointSize = size; } void APIENTRY glPointParameterfEXT(GLenum pname, GLfloat param) { switch (pname) { case GL_POINT_SIZE_MIN_EXT: if (param < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointSizeMin = param; break; case GL_POINT_SIZE_MAX_EXT: if (param < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointSizeMax = param; break; case GL_POINT_FADE_THRESHOLD_SIZE_EXT: if (param < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointFadeThresholdSize = param; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glPointParameterfvEXT(GLenum pname, const GLfloat* params) { if (!params) { g_gl.lastError = GL_INVALID_VALUE; return; } switch (pname) { case GL_POINT_SIZE_MIN_EXT: if (params[0] < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointSizeMin = params[0]; break; case GL_POINT_SIZE_MAX_EXT: if (params[0] < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointSizeMax = params[0]; break; case GL_POINT_FADE_THRESHOLD_SIZE_EXT: if (params[0] < 0.0f) { g_gl.lastError = GL_INVALID_VALUE; return; } g_gl.pointFadeThresholdSize = params[0]; break; case GL_DISTANCE_ATTENUATION_EXT: g_gl.pointDistanceAttenuation[0] = params[0]; g_gl.pointDistanceAttenuation[1] = params[1]; g_gl.pointDistanceAttenuation[2] = params[2]; break; default: g_gl.lastError = GL_INVALID_ENUM; break; } } void APIENTRY glColor3fv(const GLfloat* v) { if (!v) return; glColor3f(v[0], v[1], v[2]); } static GLuint g_qd3d12ListBaseCompat = 0; static GLfloat g_qd3d12CompatRasterPos[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; static GLboolean g_qd3d12CompatRasterPosValid = GL_TRUE; void APIENTRY glRasterPos3fv(const GLfloat* v) { if (!v) return; glRasterPos3f(v[0], v[1], v[2]); } void APIENTRY glCallLists(GLsizei n, GLenum type, const GLvoid* lists) { if (n <= 0 || !lists) return; switch (type) { case GL_UNSIGNED_BYTE: { const GLubyte* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + (GLuint)p[i]); break; } case GL_BYTE: { const GLbyte* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + (GLuint)(unsigned char)p[i]); break; } case GL_UNSIGNED_SHORT: { const GLushort* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + (GLuint)p[i]); break; } case GL_SHORT: { const GLshort* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + (GLuint)(unsigned short)p[i]); break; } case GL_UNSIGNED_INT: { const GLuint* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + p[i]); break; } case GL_INT: { const GLint* p = static_cast(lists); for (GLsizei i = 0; i < n; ++i) glCallList(g_qd3d12ListBaseCompat + (GLuint)p[i]); break; } default: g_gl.lastError = GL_INVALID_ENUM; break; } } // opengl.cpp void APIENTRY glTranslated(GLdouble x, GLdouble y, GLdouble z) { glTranslatef((GLfloat)x, (GLfloat)y, (GLfloat)z); } void APIENTRY glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) { glRotatef((GLfloat)angle, (GLfloat)x, (GLfloat)y, (GLfloat)z); } void APIENTRY glTexGenf(GLenum coord, GLenum pname, GLfloat param) { (void)coord; (void)pname; (void)param; } void APIENTRY glTexGeni(GLenum coord, GLenum pname, GLint param) { glTexGenf(coord, pname, (GLfloat)param); } void APIENTRY glTexGenfv(GLenum coord, GLenum pname, const GLfloat* params) { if (!params) return; glTexGenf(coord, pname, params[0]); } void APIENTRY glTexGeniv(GLenum coord, GLenum pname, const GLint* params) { if (!params) return; glTexGeni(coord, pname, params[0]); } void APIENTRY glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) { glBegin(GL_QUADS); glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); glEnd(); } void APIENTRY glRasterPos3f(GLfloat x, GLfloat y, GLfloat z) { g_qd3d12CompatRasterPos[0] = x; g_qd3d12CompatRasterPos[1] = y; g_qd3d12CompatRasterPos[2] = z; g_qd3d12CompatRasterPos[3] = 1.0f; g_qd3d12CompatRasterPosValid = GL_TRUE; } // ----------------------------------------------------------------------------- // Minimal legacy-GL compatibility state // ----------------------------------------------------------------------------- struct QD3D12AttribState { GLfloat currentColor[4]; GLfloat currentNormal[3]; GLfloat rasterPos[4]; GLboolean rasterPosValid; GLfloat lineWidth; GLint lineStippleFactor; GLushort lineStipplePattern; GLboolean lightingEnabled; GLboolean lineStippleEnabled; GLfloat lightModelAmbient[4]; GLuint listBase; }; static QD3D12AttribState g_glState = { {1,1,1,1}, {0,0,1}, {0,0,0,1}, GL_TRUE, 1.0f, 1, 0xFFFF, GL_FALSE, GL_FALSE, {0.2f, 0.2f, 0.2f, 1.0f}, 0 }; static std::vector g_attribStack; static GLfloat g_qd3d12PixelZoomX = 1.0f; static GLfloat g_qd3d12PixelZoomY = 1.0f; static bool QD3D12_ConvertPixelsToRGBA8(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels, std::vector& outRGBA) { if (width <= 0 || height <= 0 || !pixels) return false; if (type != GL_UNSIGNED_BYTE) return false; const uint8_t* src = static_cast(pixels); outRGBA.resize((size_t)width * (size_t)height * 4); for (GLsizei i = 0; i < width * height; ++i) { uint8_t r = 0, g = 0, b = 0, a = 255; switch (format) { case GL_RGBA: b = src[i * 4 + 2]; g = src[i * 4 + 1]; r = src[i * 4 + 0]; a = src[i * 4 + 3]; break; case GL_BGRA_EXT: r = src[i * 4 + 2]; g = src[i * 4 + 1]; b = src[i * 4 + 0]; a = src[i * 4 + 3]; break; case GL_RGB: r = src[i * 3 + 0]; g = src[i * 3 + 1]; b = src[i * 3 + 2]; break; case GL_BGR_EXT: r = src[i * 3 + 2]; g = src[i * 3 + 1]; b = src[i * 3 + 0]; break; case GL_LUMINANCE: r = g = b = src[i]; break; case GL_ALPHA: a = src[i]; r = g = b = 255; break; case GL_LUMINANCE_ALPHA: r = g = b = src[i * 2 + 0]; a = src[i * 2 + 1]; break; default: return false; } outRGBA[(size_t)i * 4 + 0] = r; outRGBA[(size_t)i * 4 + 1] = g; outRGBA[(size_t)i * 4 + 2] = b; outRGBA[(size_t)i * 4 + 3] = a; } return true; } // ----------------------------------------------------------------------------- // Raster position // ----------------------------------------------------------------------------- void APIENTRY glRasterPos2f(GLfloat x, GLfloat y) { g_qd3d12CompatRasterPos[0] = x; g_qd3d12CompatRasterPos[1] = y; g_qd3d12CompatRasterPos[2] = 0.0f; g_qd3d12CompatRasterPos[3] = 1.0f; g_qd3d12CompatRasterPosValid = GL_TRUE; } void APIENTRY glPixelZoom(GLfloat xfactor, GLfloat yfactor) { g_qd3d12PixelZoomX = xfactor; g_qd3d12PixelZoomY = yfactor; } void APIENTRY glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels) { if (!g_qd3d12CompatRasterPosValid || width <= 0 || height <= 0 || !pixels) return; std::vector rgba; if (!QD3D12_ConvertPixelsToRGBA8(width, height, format, type, pixels, rgba)) { g_gl.lastError = GL_INVALID_ENUM; return; } const GLuint unit = g_gl.activeTextureUnit; const GLuint oldBound = g_gl.boundTexture[unit]; const bool oldTexture2D = g_gl.texture2D[unit]; const float oldColor[4] = { g_gl.curColor[0], g_gl.curColor[1], g_gl.curColor[2], g_gl.curColor[3] }; GLuint tempTexture = 0; glGenTextures(1, &tempTexture); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tempTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data()); const GLfloat x1 = g_qd3d12CompatRasterPos[0]; const GLfloat y1 = g_qd3d12CompatRasterPos[1]; const GLfloat x2 = x1 + (GLfloat)width * g_qd3d12PixelZoomX; const GLfloat y2 = y1 + (GLfloat)height * g_qd3d12PixelZoomY; glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(x1, y1); glTexCoord2f(1.0f, 0.0f); glVertex2f(x2, y1); glTexCoord2f(1.0f, 1.0f); glVertex2f(x2, y2); glTexCoord2f(0.0f, 1.0f); glVertex2f(x1, y2); glEnd(); glDeleteTextures(1, &tempTexture); if (oldTexture2D) glEnable(GL_TEXTURE_2D); else glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, oldBound); glColor4f(oldColor[0], oldColor[1], oldColor[2], oldColor[3]); } // ----------------------------------------------------------------------------- // Current color / normal // ----------------------------------------------------------------------------- void APIENTRY glNormal3f(GLfloat x, GLfloat y, GLfloat z) { g_gl.curNormal[0] = x; g_gl.curNormal[1] = y; g_gl.curNormal[2] = z; g_glState.currentNormal[0] = x; g_glState.currentNormal[1] = y; g_glState.currentNormal[2] = z; } void APIENTRY glNormal3fv(const GLfloat* v) { if (!v) return; glNormal3f(v[0], v[1], v[2]); } void APIENTRY glTangent3f(GLfloat x, GLfloat y, GLfloat z) { g_gl.curTangent[0] = x; g_gl.curTangent[1] = y; g_gl.curTangent[2] = z; } void APIENTRY glTangent3fv(const GLfloat* v) { if (!v) return; glTangent3f(v[0], v[1], v[2]); } void APIENTRY glBinormal3f(GLfloat x, GLfloat y, GLfloat z) { g_gl.curBinormal[0] = x; g_gl.curBinormal[1] = y; g_gl.curBinormal[2] = z; } void APIENTRY glBinormal3fv(const GLfloat* v) { if (!v) return; glBinormal3f(v[0], v[1], v[2]); } // ----------------------------------------------------------------------------- // Attribute stack // ----------------------------------------------------------------------------- void APIENTRY glPushAttrib(GLbitfield mask) { (void)mask; g_attribStack.push_back(g_glState); } void APIENTRY glPopAttrib(void) { if (g_attribStack.empty()) return; g_glState = g_attribStack.back(); g_attribStack.pop_back(); g_gl.curNormal[0] = g_glState.currentNormal[0]; g_gl.curNormal[1] = g_glState.currentNormal[1]; g_gl.curNormal[2] = g_glState.currentNormal[2]; } // ----------------------------------------------------------------------------- // Misc legacy state // ----------------------------------------------------------------------------- GLboolean APIENTRY glIsEnabled(GLenum cap) { switch (cap) { case GL_VERTEX_PROGRAM_ARB: return QD3D12ARB_IsVertexEnabled() ? GL_TRUE : GL_FALSE; case GL_FRAGMENT_PROGRAM_ARB: return QD3D12ARB_IsFragmentEnabled() ? GL_TRUE : GL_FALSE; case GL_BLEND: return g_gl.blend ? GL_TRUE : GL_FALSE; case GL_ALPHA_TEST: return g_gl.alphaTest ? GL_TRUE : GL_FALSE; case GL_DEPTH_TEST: return g_gl.depthTest ? GL_TRUE : GL_FALSE; case GL_CULL_FACE: return g_gl.cullFace ? GL_TRUE : GL_FALSE; case GL_SCISSOR_TEST: return g_gl.scissorTest ? GL_TRUE : GL_FALSE; case GL_STENCIL_TEST: return g_gl.stencilTest ? GL_TRUE : GL_FALSE; case GL_FOG: return g_gl.fog ? GL_TRUE : GL_FALSE; #ifdef GL_DEPTH_BOUNDS_TEST_EXT case GL_DEPTH_BOUNDS_TEST_EXT: return g_gl.depthBoundsTest ? GL_TRUE : GL_FALSE; #endif case GL_POLYGON_OFFSET_POINT: return g_gl.polygonOffsetPoint ? GL_TRUE : GL_FALSE; case GL_POLYGON_OFFSET_LINE: return g_gl.polygonOffsetLine ? GL_TRUE : GL_FALSE; case GL_POLYGON_OFFSET_FILL: return g_gl.polygonOffsetFill ? GL_TRUE : GL_FALSE; #ifdef GL_STENCIL_TEST_TWO_SIDE_EXT case GL_STENCIL_TEST_TWO_SIDE_EXT: return g_gl.stencilTwoSide ? GL_TRUE : GL_FALSE; #endif case GL_TEXTURE_2D: return g_gl.texture2D[g_gl.activeTextureUnit] ? GL_TRUE : GL_FALSE; case GL_QD3D12_TAA: return QD3D12_IsTAAEnabled() ? GL_TRUE : GL_FALSE; case GL_LIGHTING: return g_glState.lightingEnabled; case GL_LINE_STIPPLE: return g_glState.lineStippleEnabled; default: return GL_FALSE; } } void APIENTRY glLineWidth(GLfloat width) { g_glState.lineWidth = (width > 0.0f) ? width : 1.0f; } void APIENTRY glLineStipple(GLint factor, GLushort pattern) { g_glState.lineStippleFactor = (factor > 0) ? factor : 1; g_glState.lineStipplePattern = pattern; } void APIENTRY glLightModelfv(GLenum pname, const GLfloat* params) { if (!params) return; if (pname == GL_LIGHT_MODEL_AMBIENT) { g_glState.lightModelAmbient[0] = params[0]; g_glState.lightModelAmbient[1] = params[1]; g_glState.lightModelAmbient[2] = params[2]; g_glState.lightModelAmbient[3] = params[3]; } } void APIENTRY glPolygonStipple(const GLubyte* mask) { (void)mask; // Stub: keep for compatibility. Real implementation only matters // if your renderer actually emulates polygon stipple. } // ----------------------------------------------------------------------------- // Display list stubs // ----------------------------------------------------------------------------- static GLuint g_nextListId = 1; static GLuint g_currentList = 0; static GLenum g_currentListMode = 0; GLuint APIENTRY glGenLists(GLsizei range) { if (range <= 0) return 0; GLuint first = g_nextListId; g_nextListId += (GLuint)range; return first; } void APIENTRY glNewList(GLuint list, GLenum mode) { g_currentList = list; g_currentListMode = mode; } void APIENTRY glEndList(void) { g_currentList = 0; g_currentListMode = 0; } void APIENTRY glCallList(GLuint list) { (void)list; // Stub. Needed for link/compile. // Replace with recorded command playback if you add true list support. } void APIENTRY glDeleteLists(GLuint list, GLsizei range) { (void)list; (void)range; } void APIENTRY glListBase(GLuint base) { g_glState.listBase = base; g_qd3d12ListBaseCompat = base; } void APIENTRY glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) { (void)x; (void)y; (void)width; (void)height; (void)type; // Stub. Often only needed for old UI or editor paths. } static void ExpandImmediateSlow(GLenum mode, ImmediateVertexBuffer& src, std::vector& out) { const size_t n = src.count; switch (mode) { case GL_TRIANGLES: { out.resize(g_gl.immediateVerts.count); for (int i = 0; i < g_gl.immediateVerts.count; i++) { out[i].xyz[0] = g_gl.immediateVerts.Data()[i].px; out[i].xyz[1] = g_gl.immediateVerts.Data()[i].py; out[i].xyz[2] = g_gl.immediateVerts.Data()[i].pz; out[i].normal[0] = g_gl.immediateVerts.Data()[i].nx; out[i].normal[1] = g_gl.immediateVerts.Data()[i].ny; out[i].normal[2] = g_gl.immediateVerts.Data()[i].nz; out[i].st[0] = g_gl.immediateVerts.Data()[i].u0; out[i].st[1] = g_gl.immediateVerts.Data()[i].v0; } return; } case GL_TRIANGLE_STRIP: { if (n < 3) { out.clear(); return; } const size_t triCount = n - 2; out.resize(triCount * 3); size_t d = 0; for (size_t i = 2; i < n; ++i, d += 3) { if ((i & 1) == 0) { out[d + 0] = src.Data()[i - 2]; out[d + 1] = src.Data()[i - 1]; out[d + 2] = src.Data()[i]; } else { out[d + 0] = src.Data()[i - 1]; out[d + 1] = src.Data()[i - 2]; out[d + 2] = src.Data()[i]; } } return; } case GL_TRIANGLE_FAN: { if (n < 3) { out.clear(); return; } const size_t triCount = n - 2; out.resize(triCount * 3); const GLVertex v0 = src.Data()[0]; size_t d = 0; for (size_t i = 2; i < n; ++i, d += 3) { out[d + 0] = v0; out[d + 1] = src.Data()[i - 1]; out[d + 2] = src.Data()[i]; } return; } case GL_QUADS: { const size_t quadCount = n >> 2; out.resize(quadCount * 6); size_t d = 0; for (size_t i = 0; i + 3 < n; i += 4, d += 6) { const GLVertex& v0 = src.Data()[i + 0]; const GLVertex& v1 = src.Data()[i + 1]; const GLVertex& v2 = src.Data()[i + 2]; const GLVertex& v3 = src.Data()[i + 3]; out[d + 0] = v0; out[d + 1] = v1; out[d + 2] = v2; out[d + 3] = v0; out[d + 4] = v2; out[d + 5] = v3; } return; } case GL_QUAD_STRIP: { if (n < 4) { out.clear(); return; } const size_t quadCount = (n - 2) >> 1; out.resize(quadCount * 6); size_t d = 0; for (size_t i = 0; i + 3 < n; i += 2, d += 6) { const GLVertex& v0 = src.Data()[i + 0]; const GLVertex& v1 = src.Data()[i + 1]; const GLVertex& v2 = src.Data()[i + 2]; const GLVertex& v3 = src.Data()[i + 3]; out[d + 0] = v0; out[d + 1] = v1; out[d + 2] = v2; out[d + 3] = v2; out[d + 4] = v1; out[d + 5] = v3; } return; } // case GL_POLYGON: // { // // This is still going to be the expensive path. // TessellatePolygon(src, out); // return; // } default: { assert(!"Unknown ExpandImmediate type!"); out.clear(); return; } } } void glUpdateBottomAccelStructure(bool opaque, uint32_t& meshHandle) { std::vector drawVertexes; ExpandImmediateSlow(g_gl.currentPrim, g_gl.immediateVerts, drawVertexes); std::vector drawIndexes(drawVertexes.size()); for (int i = 0; i < drawVertexes.size(); i++) { drawIndexes[i] = i; } glRaytracingMeshDesc_t meshDesc; memset(&meshDesc, 0, sizeof(meshDesc)); meshDesc.vertices = &drawVertexes[0]; meshDesc.vertexCount = (uint32_t)drawVertexes.size(); meshDesc.indices = &drawIndexes[0]; meshDesc.indexCount = (uint32_t)drawIndexes.size(); meshDesc.allowUpdate = 1; const uint32_t materialFlags = QD3D12_CurrentEffectiveRayMaterialFlags(); const bool isGlass = QD3D12_RayMaterialFlagsHaveGlass(materialFlags); // Glass/alpha-blended surfaces must be non-opaque in the BLAS so the DXR // any-hit shader can run IgnoreHit() and let visibility/path rays continue // through the pane/sprite without requiring a separate caller-side tag. meshDesc.opaque = (opaque && !isGlass) ? 1 : 0; if (meshHandle) glRaytracingUpdateMesh(meshHandle, &meshDesc); else meshHandle = glRaytracingCreateMesh(&meshDesc); if (meshHandle) { g_qd3d12RaytracingMeshMaterialFlags[meshHandle] = materialFlags; glRaytracingSetMeshMaterialFlags((glRaytracingMeshHandle_t)meshHandle, materialFlags); } } int glSetTopLevelAccelStructureVisible( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle, int visible) { if (scene == 0 || topLevelHandle == 0) return 0; return glRaytracingSetInstanceVisibilityInScene( scene, (glRaytracingInstanceHandle_t)topLevelHandle, visible ? 1 : 0); } void glHideTopLevelAccelStructure( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { (void)glSetTopLevelAccelStructureVisible(scene, topLevelHandle, 0); } void glShowTopLevelAccelStructure( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { (void)glSetTopLevelAccelStructureVisible(scene, topLevelHandle, 1); } int glIsTopLevelAccelStructureVisible( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { if (scene == 0 || topLevelHandle == 0) return 0; return glRaytracingGetInstanceVisibilityInScene( scene, (glRaytracingInstanceHandle_t)topLevelHandle) ? 1 : 0; } void glHideAllTopLevelAccelStructures(glRaytracingSceneHandle_t scene) { if (scene == 0) { glRaytracingHideAllInstances(); return; } glRaytracingHideAllInstancesInScene(scene); } void glShowAllTopLevelAccelStructures(glRaytracingSceneHandle_t scene) { if (scene == 0) { glRaytracingShowAllInstances(); return; } glRaytracingShowAllInstancesInScene(scene); } int glSetTopLevelAceelStructureVisible( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle, int visible) { return glSetTopLevelAccelStructureVisible(scene, topLevelHandle, visible); } void glHideTopLevelAceelStructure( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { glHideTopLevelAccelStructure(scene, topLevelHandle); } void glShowTopLevelAceelStructure( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { glShowTopLevelAccelStructure(scene, topLevelHandle); } int glIsTopLevelAceelStructureVisible( glRaytracingSceneHandle_t scene, uint32_t topLevelHandle) { return glIsTopLevelAccelStructureVisible(scene, topLevelHandle); } void glHideAllTopLevelAceelStructures(glRaytracingSceneHandle_t scene) { glHideAllTopLevelAccelStructures(scene); } void glShowAllTopLevelAceelStructures(glRaytracingSceneHandle_t scene) { glShowAllTopLevelAccelStructures(scene); } void glUpdateTopLevelAceelStructure( glRaytracingSceneHandle_t scene, uint32_t mesh, float* transform, uint32_t& topLevelHandle) { if (scene == 0 || mesh == 0) return; glRaytracingInstanceDesc_t instDesc = {}; instDesc.meshHandle = (glRaytracingMeshHandle_t)mesh; const uint32_t materialFlags = QD3D12_GetRaytracingMeshMaterialFlags(mesh); instDesc.instanceID = QD3D12_EncodeRaytracingInstanceId(0u, materialFlags); // Default for newly-created instances. Existing hidden instances preserve // their current mask below instead of being forced visible by this update path. instDesc.mask = 0xFFu; if (transform == NULL) { instDesc.transform[0] = 1.0f; instDesc.transform[1] = 0.0f; instDesc.transform[2] = 0.0f; instDesc.transform[3] = 0.0f; instDesc.transform[4] = 0.0f; instDesc.transform[5] = 1.0f; instDesc.transform[6] = 0.0f; instDesc.transform[7] = 0.0f; instDesc.transform[8] = 0.0f; instDesc.transform[9] = 0.0f; instDesc.transform[10] = 1.0f; instDesc.transform[11] = 0.0f; } else { // Same contract as the old helper: transform is a 12-float // D3D12_RAYTRACING_INSTANCE_DESC-compatible 3x4 transform. memcpy(instDesc.transform, transform, sizeof(float) * 12); } if (topLevelHandle == 0) { instDesc.mask = 0xFFu; topLevelHandle = glRaytracingCreateInstanceInScene(scene, &instDesc); return; } // Preserve the current hide/show state across transform/material updates. const int wasVisible = glRaytracingGetInstanceVisibilityInScene( scene, (glRaytracingInstanceHandle_t)topLevelHandle); instDesc.mask = wasVisible ? 0xFFu : 0u; if (!glRaytracingUpdateInstanceInScene( scene, (glRaytracingInstanceHandle_t)topLevelHandle, &instDesc)) { // Stale handle path: create a fresh visible instance. instDesc.mask = 0xFFu; topLevelHandle = glRaytracingCreateInstanceInScene(scene, &instDesc); } }