Files
DoomRTX/neo/engine/opengl/gl_d3d12shim.cpp
T
Justin Marshall 754f5b4aa0 POM Relief Mapping
2026-05-24 18:27:11 -07:00

20259 lines
616 KiB
C++

/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
IceTech Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
// gl_d3d12shim.cpp
//
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <wrl/client.h>
#include <stdint.h>
#include <vector>
#include <string>
#include <unordered_map>
#include <array>
#include <algorithm>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <cmath>
#include <immintrin.h>
#include <stdint.h>
#include <vector>
#include <string.h>
#include <algorithm>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <d3d12.h>
#include <d3dcompiler.h>
#include <dxgi1_6.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
#include <string>
#include <vector>
#include <dxcapi.h>
#define QD3D12_ENABLE_STREAMLINE
#if defined(QD3D12_ENABLE_STREAMLINE)
#include <sl.h>
#include <sl_consts.h>
#include <sl_dlss.h>
#include <sl_dlss_d.h>
#include <sl_dlss_g.h>
#include <sl_reflex.h>
#endif
#if defined(QD3D12_ENABLE_FFX)
#include <ffx_api/ffx_api.hpp>
#include <ffx_api/ffx_upscale.hpp>
#include <ffx_api/dx12/ffx_api_dx12.hpp>
#include <ffx_api/ffx_denoiser.hpp>
#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_FRAME_GENERATION
#define GL_QD3D12_FRAME_GENERATION 0x600E
#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 glTextureAverageColorQD3D12(GLuint texture, GLfloat r, GLfloat g, GLfloat b, GLfloat a);
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);
void glRaytracingSetMeshAverageColor(glRaytracingMeshHandle_t meshHandle, float r, float g, float b, float a);
// 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 QD3D12_SetFrameGenerationMultiplier(int multiplier);
int QD3D12_GetFrameGenerationMultiplier(void);
int QD3D12_GetActiveFrameGenerationMultiplier(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* worldToView,
const float* viewToWorld,
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_WaitForGPU();
static void QD3D12_EnsureFrameOpen();
static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w);
static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w);
static void QD3D12_ExecuteMainCommandListForRaytracingHandoff(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 = 16;
static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount;
static constexpr UINT QD3D12_PostConstantDwords = 28;
static constexpr UINT QD3D12_ToneMapTileDim = 64;
static constexpr UINT QD3D12_MaxScreenSpaceDecals = 64;
static constexpr UINT QD3D12_MaxScreenSpaceParticleLights = GL_RAYTRACING_MAX_LIGHTS;
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();
Mat4 worldToView = Mat4::Identity();
Mat4 viewToWorld = 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;
};
struct QD3D12ScreenSpaceParticleBatch
{
std::vector<glScreenSpaceParticleVertex_t> vertices;
uint32_t textureId = 0;
uint32_t blendMode = 0;
float softDepth = 16.0f;
float emissiveScale = 0.0f;
};
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<ID3D12Resource> resource;
};
static std::vector<RetiredResource> g_retiredResources;
struct FrameResources
{
ComPtr<ID3D12CommandAllocator> cmdAlloc;
UINT64 fenceValue = 0;
};
struct UploadRing
{
ComPtr<ID3D12Resource> 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;
D3D12_RESOURCE_STATES weightsState = D3D12_RESOURCE_STATE_COMMON;
D3D12_RESOURCE_STATES latentState = D3D12_RESOURCE_STATE_COMMON;
UINT weightsSrvIndex = UINT_MAX;
UINT latentSrvIndex = UINT_MAX;
D3D12_CPU_DESCRIPTOR_HANDLE weightsSrvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE weightsSrvGpu{};
D3D12_CPU_DESCRIPTOR_HANDLE latentSrvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE latentSrvGpu{};
ComPtr<ID3D12Resource> weightsBuffer;
ComPtr<ID3D12Resource> latentBuffer;
std::vector<uint8_t> weightsBytes;
std::vector<uint8_t> latentRGBA16FBytes;
};
struct TextureResource
{
GLuint glId = 0;
int width = 0;
int height = 0;
UINT srvIndex = UINT_MAX;
UINT mipLevels = 1;
GLenum format = GL_RGBA;
GLenum minFilter = GL_LINEAR;
GLenum magFilter = GL_LINEAR;
GLenum wrapS = GL_REPEAT;
GLenum wrapT = GL_REPEAT;
GLenum compressedInternalFormat = 0;
UINT compressedBlockBytes = 0;
UINT compressedImageSize = 0;
DXGI_FORMAT dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
bool compressed = false;
bool forceOpaqueAlpha = false;
bool gpuValid = false;
bool isNormalMap = false;
bool isGlowMap = false;
bool isSpecularMap = false;
float averageColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
ComPtr<ID3D12Resource> texture;
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
D3D12_CPU_DESCRIPTOR_HANDLE srvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE srvGpu{};
// 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<uint8_t> sysmem;
std::vector<std::vector<uint8_t>> pendingMipChain;
// 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 void EnsureTextureResource(TextureResource& tex);
static void UploadTexture(TextureResource& tex, ID3D12GraphicsCommandList* commandList = nullptr);
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];
float currentColor[4];
float vertexColorPad[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;
GLenum usage = GL_STATIC_DRAW_ARB;
std::vector<uint8_t> data;
uint32_t revision = 1;
bool mapped = false;
GLintptr mappedOffset = 0;
GLsizeiptr mappedLength = 0;
GLbitfield mappedAccess = 0;
ComPtr<ID3D12Resource> resource;
uint8_t* mappedGpu = nullptr;
D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = 0;
size_t gpuBytes = 0;
ComPtr<ID3D12Resource> packedVertexResource;
uint8_t* packedVertexMapped = nullptr;
D3D12_GPU_VIRTUAL_ADDRESS packedVertexGpuAddress = 0;
size_t packedVertexBytes = 0;
size_t packedVertexCount = 0;
uint32_t packedVertexRevision = 0;
uint64_t packedVertexLayoutHash = 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
{
uint64_t fullHash = 0;
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;
std::shared_ptr<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 };
float currentColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float useVertexColor = 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 inline void QD3D12_MixHash(uint64_t& h, uint64_t v)
{
h ^= v;
h *= 1099511628211ull;
}
static inline void QD3D12_MixHashFloat(uint64_t& h, float f)
{
uint32_t bits = 0;
memcpy(&bits, &f, sizeof(bits));
QD3D12_MixHash(h, bits);
}
static inline void QD3D12_MixHashBytes(uint64_t& h, const void* data, size_t size)
{
const uint8_t* bytes = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < size; ++i)
QD3D12_MixHash(h, bytes[i]);
}
static uint64_t QD3D12_HashBatchKey(const BatchKey& key)
{
uint64_t h = 1469598103934665603ull;
QD3D12_MixHashBytes(h, &key.viewport, sizeof(key.viewport));
QD3D12_MixHashBytes(h, &key.scissor, sizeof(key.scissor));
QD3D12_MixHash(h, uint32_t(key.pipeline));
QD3D12_MixHash(h, uint32_t(key.topology));
QD3D12_MixHash(h, key.useTessellation ? 1ull : 0ull);
QD3D12_MixHash(h, key.tex0SrvIndex);
QD3D12_MixHash(h, key.tex1SrvIndex);
QD3D12_MixHashBytes(h, key.textureSrvIndex, sizeof(key.textureSrvIndex));
QD3D12_MixHash(h, key.normalMapSrvIndex);
QD3D12_MixHashFloat(h, key.useNormalMap);
QD3D12_MixHashFloat(h, key.normalMapStrength);
QD3D12_MixHashFloat(h, key.normalMapYSign);
QD3D12_MixHash(h, key.glowMapSrvIndex);
QD3D12_MixHashFloat(h, key.useGlowMap);
QD3D12_MixHashFloat(h, key.glowMapStrength);
QD3D12_MixHash(h, key.specularMapSrvIndex);
QD3D12_MixHashFloat(h, key.useSpecularMap);
QD3D12_MixHashFloat(h, key.specularMapStrength);
QD3D12_MixHash(h, key.neuralPOMWeightsSrvIndex);
QD3D12_MixHash(h, key.neuralPOMLatentSrvIndex);
QD3D12_MixHashFloat(h, key.useNeuralPOM);
QD3D12_MixHash(h, key.useARBPrograms ? 1ull : 0ull);
QD3D12_MixHash(h, key.arbVertexProgram);
QD3D12_MixHash(h, key.arbFragmentProgram);
QD3D12_MixHash(h, key.arbVertexRevision);
QD3D12_MixHash(h, key.arbFragmentRevision);
QD3D12_MixHash(h, reinterpret_cast<uintptr_t>(key.arbVertexBlob));
QD3D12_MixHash(h, reinterpret_cast<uintptr_t>(key.arbFragmentBlob));
if (key.useARBPrograms && key.arbConstants)
QD3D12_MixHashBytes(h, key.arbConstants.get(), sizeof(*key.arbConstants));
QD3D12_MixHashBytes(h, key.texComb0RGB, sizeof(key.texComb0RGB));
QD3D12_MixHashBytes(h, key.texComb0Alpha, sizeof(key.texComb0Alpha));
QD3D12_MixHashBytes(h, key.texComb0Operand, sizeof(key.texComb0Operand));
QD3D12_MixHashBytes(h, key.texEnvColor0, sizeof(key.texEnvColor0));
QD3D12_MixHashBytes(h, key.texComb1RGB, sizeof(key.texComb1RGB));
QD3D12_MixHashBytes(h, key.texComb1Alpha, sizeof(key.texComb1Alpha));
QD3D12_MixHashBytes(h, key.texComb1Operand, sizeof(key.texComb1Operand));
QD3D12_MixHashBytes(h, key.texEnvColor1, sizeof(key.texEnvColor1));
QD3D12_MixHash(h, key.colorWriteMask);
QD3D12_MixHash(h, key.cullFaceEnabled ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(key.cullMode));
QD3D12_MixHash(h, uint32_t(key.frontFace));
QD3D12_MixHash(h, key.stencilTest ? 1ull : 0ull);
QD3D12_MixHash(h, key.stencilReadMask);
QD3D12_MixHash(h, key.stencilWriteMask);
QD3D12_MixHash(h, key.stencilRef);
QD3D12_MixHash(h, uint32_t(key.stencilFrontFunc));
QD3D12_MixHash(h, uint32_t(key.stencilFrontSFail));
QD3D12_MixHash(h, uint32_t(key.stencilFrontDPFail));
QD3D12_MixHash(h, uint32_t(key.stencilFrontDPPass));
QD3D12_MixHash(h, uint32_t(key.stencilBackFunc));
QD3D12_MixHash(h, uint32_t(key.stencilBackSFail));
QD3D12_MixHash(h, uint32_t(key.stencilBackDPFail));
QD3D12_MixHash(h, uint32_t(key.stencilBackDPPass));
QD3D12_MixHash(h, key.depthBoundsTest ? 1ull : 0ull);
QD3D12_MixHashFloat(h, key.depthBoundsMin);
QD3D12_MixHashFloat(h, key.depthBoundsMax);
QD3D12_MixHash(h, key.polygonOffsetEnabled ? 1ull : 0ull);
QD3D12_MixHashFloat(h, key.polygonOffsetFactor);
QD3D12_MixHashFloat(h, key.polygonOffsetUnits);
QD3D12_MixHashFloat(h, key.alphaRef);
QD3D12_MixHashFloat(h, key.alphaFunc);
QD3D12_MixHashFloat(h, key.useTex0);
QD3D12_MixHashFloat(h, key.useTex1);
QD3D12_MixHashFloat(h, key.tex1IsLightmap);
QD3D12_MixHashFloat(h, key.texEnvMode0);
QD3D12_MixHashFloat(h, key.texEnvMode1);
QD3D12_MixHashFloat(h, key.fogEnabled);
QD3D12_MixHashFloat(h, key.fogMode);
QD3D12_MixHashFloat(h, key.fogDensity);
QD3D12_MixHashFloat(h, key.fogStart);
QD3D12_MixHashFloat(h, key.fogEnd);
QD3D12_MixHashBytes(h, key.fogColor, sizeof(key.fogColor));
QD3D12_MixHashBytes(h, key.currentColor, sizeof(key.currentColor));
QD3D12_MixHashFloat(h, key.useVertexColor);
QD3D12_MixHash(h, uint32_t(key.blendSrc));
QD3D12_MixHash(h, uint32_t(key.blendDst));
QD3D12_MixHash(h, key.depthTest ? 1ull : 0ull);
QD3D12_MixHash(h, key.depthWrite ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(key.depthFunc));
QD3D12_MixHashBytes(h, key.mvp.m, sizeof(key.mvp.m));
QD3D12_MixHashBytes(h, key.prevMvp.m, sizeof(key.prevMvp.m));
QD3D12_MixHashBytes(h, key.modelMatrix.m, sizeof(key.modelMatrix.m));
QD3D12_MixHashFloat(h, key.geometryFlag);
QD3D12_MixHashFloat(h, key.roughness);
QD3D12_MixHashFloat(h, key.materialType);
QD3D12_MixHash(h, key.motionObjectId);
QD3D12_MixHashBytes(h, key.cameraWorldPos, sizeof(key.cameraWorldPos));
QD3D12_MixHashFloat(h, key.cameraValid);
return h ? h : 1ull;
}
static inline void QD3D12_FinalizeBatchKeyHash(BatchKey& key)
{
key.fullHash = QD3D12_HashBatchKey(key);
}
static bool BatchKeyDeepEquals(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 &&
memcmp(a.currentColor, b.currentColor, sizeof(a.currentColor)) == 0 &&
a.useVertexColor == b.useVertexColor &&
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 ||
(a.arbConstants && b.arbConstants &&
memcmp(a.arbConstants.get(), b.arbConstants.get(), 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;
}
static bool BatchKeyEquals(const BatchKey& a, const BatchKey& b)
{
if (a.fullHash != 0 && b.fullHash != 0)
{
#if defined(_DEBUG)
if (a.fullHash == b.fullHash)
assert(BatchKeyDeepEquals(a, b));
#endif
return a.fullHash == b.fullHash;
}
return BatchKeyDeepEquals(a, b);
}
struct QueuedBatch
{
size_t firstVertex;
size_t vertexCount;
size_t markerBegin = 0;
size_t markerEnd = 0;
UINT gpuIndexCount = 0;
bool gpuIndexed = false;
D3D12_VERTEX_BUFFER_VIEW gpuVbv{};
D3D12_INDEX_BUFFER_VIEW gpuIbv{};
ComPtr<ID3D12Resource> gpuVertexResource;
ComPtr<ID3D12Resource> gpuIndexResource;
BatchKey key;
};
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<IDXGISwapChain3> swapChain;
UINT frameIndex = 0;
ComPtr<ID3D12DescriptorHeap> 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<ComPtr<ID3D12Resource>, QD3D12_FrameCount> sceneColorBuffers;
D3D12_RESOURCE_STATES sceneColorState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> normalBuffers;
D3D12_RESOURCE_STATES normalBufferState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> positionBuffers;
D3D12_RESOURCE_STATES positionBufferState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> velocityBuffers;
D3D12_RESOURCE_STATES velocityBufferState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> emissiveBuffers;
D3D12_RESOURCE_STATES emissiveBufferState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> specularBuffers;
D3D12_RESOURCE_STATES specularBufferState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> sceneColorMsaaBuffers;
D3D12_RESOURCE_STATES sceneColorMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> normalMsaaBuffers;
D3D12_RESOURCE_STATES normalMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> positionMsaaBuffers;
D3D12_RESOURCE_STATES positionMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> velocityMsaaBuffers;
D3D12_RESOURCE_STATES velocityMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> emissiveMsaaBuffers;
D3D12_RESOURCE_STATES emissiveMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> specularMsaaBuffers;
D3D12_RESOURCE_STATES specularMsaaState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, 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<ComPtr<ID3D12Resource>, 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<ComPtr<ID3D12Resource>, 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<ComPtr<ID3D12Resource>, QD3D12_FrameCount> toneMapTileMaxBuffers;
D3D12_RESOURCE_STATES toneMapTileMaxState[QD3D12_FrameCount] = {};
std::array<ComPtr<ID3D12Resource>, QD3D12_FrameCount> toneMapSceneMaxBuffers;
D3D12_RESOURCE_STATES toneMapSceneMaxState[QD3D12_FrameCount] = {};
ComPtr<ID3D12DescriptorHeap> dsvHeap;
ComPtr<ID3D12Resource> depthBuffer;
D3D12_RESOURCE_STATES depthState = D3D12_RESOURCE_STATE_DEPTH_WRITE;
ComPtr<ID3D12Resource> depthMsaaBuffer;
D3D12_RESOURCE_STATES depthMsaaState = D3D12_RESOURCE_STATE_DEPTH_WRITE;
ComPtr<ID3D12Resource> 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();
}
struct ImmediateVertexBuffer
{
std::vector<GLVertex> 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* ResizeForWrite(size_t newCount)
{
if (newCount > storage.size())
{
size_t newSize = storage.empty() ? 1024 : storage.size();
while (newSize < newCount)
newSize *= 2;
storage.resize(newSize);
ptr = storage.data();
}
count = newCount;
return ptr;
}
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 QD3D12PreparedImmediateTextures
{
TextureResource* boundTextures[QD3D12_MaxTextureUnits] = {};
TextureResource* normalMapTex = nullptr;
TextureResource* glowMapTex = nullptr;
TextureResource* specularMapTex = nullptr;
uint64_t stamp = 0;
};
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<GLuint, GLOcclusionQuery> queries;
GLuint nextQueryId = 1;
GLuint currentQuery = 0;
std::vector<QueryMarker> queryMarkers;
ComPtr<ID3D12QueryHeap> occlusionQueryHeap;
ComPtr<ID3D12Resource> occlusionReadback;
uint64_t* occlusionReadbackCpu = nullptr;
std::unordered_map<GLuint, GLBufferObject> 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<QueuedBatch> 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;
ComPtr<ID3D12CommandAllocator> raytraceContinuationAlloc[QD3D12_FrameCount];
UINT64 raytraceContinuationFence[QD3D12_FrameCount] = {};
UINT raytraceContinuationIndex = 0;
int activeRaytraceContinuationSlot = -1;
GLuint currentMotionObjectId = 0;
float currentSurfaceRoughness = 0.5f;
float currentMaterialType = 0.0f;
uint32_t currentRayMaterialFlags = 0;
std::unordered_map<GLuint, Mat4> prevObjectMVPs;
std::unordered_map<GLuint, Mat4> currObjectMVPs;
uint64_t frameSerial = 0;
#if defined(QD3D12_ENABLE_STREAMLINE)
sl::FrameToken* streamlineFrameToken = nullptr;
uint64_t streamlineFrameTokenSerial = UINT64_MAX;
uint64_t streamlineConstantsSerial = UINT64_MAX;
#endif
bool motionHistoryReset = true;
QD3D12UpscalerBackend upscalerBackend = QD3D12_UPSCALER_DLSS;
QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_QUALITY;
bool enableInternalTAA = false;
bool enableRayAIDenoise = false;
bool enableDLSSRayReconstruction = false;
bool enableFSRRayRegeneration = false;
bool blockLightingUpscaleInputThisFrame = false;
uint32_t frameGenerationMultiplier = 0;
uint32_t pathTracingSamplesPerPixel = 1;
uint32_t pathTracingFallbackSamplesPerPixel = 2;
uint32_t pathTracingMaxBounces = 3;
glScreenSpaceDecal_t screenSpaceDecals[QD3D12_MaxScreenSpaceDecals] = {};
uint32_t screenSpaceDecalCount = 0;
std::vector<QD3D12ScreenSpaceParticleBatch> screenSpaceParticleBatches;
glRaytracingLight_t screenSpaceParticleLights[QD3D12_MaxScreenSpaceParticleLights] = {};
uint32_t screenSpaceParticleLightCount = 0;
ComPtr<ID3D12Resource> screenSpaceDecalBuffer;
void* screenSpaceDecalBufferMapped = nullptr;
UINT screenSpaceDecalSrvIndex = UINT_MAX;
D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvGpu{};
ComPtr<ID3D12Resource> screenSpaceParticleLightBuffer;
void* screenSpaceParticleLightBufferMapped = nullptr;
UINT screenSpaceParticleLightSrvIndex = UINT_MAX;
D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceParticleLightSrvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceParticleLightSrvGpu{};
UINT screenSpaceParticleTlasSrvIndex = UINT_MAX;
D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceParticleTlasSrvCpu{};
D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceParticleTlasSrvGpu{};
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;
GLuint buffer = 0;
size_t offset = 0;
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 alphaFuncMapped = 4.0f;
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;
BatchKey cachedImmediateBatchKey{};
uint64_t cachedImmediateBatchStamp = 0;
bool cachedImmediateBatchKeyValid = false;
QD3D12PreparedImmediateTextures cachedImmediateTextures{};
uint64_t cachedImmediateTextureStamp = 0;
bool cachedImmediateTexturesValid = false;
GLenum matrixMode = GL_MODELVIEW;
std::vector<Mat4> modelStack{ Mat4::Identity() };
std::vector<Mat4> projStack{ Mat4::Identity() };
std::vector<Mat4> texStack[QD3D12_MaxTextureUnits] = { { Mat4::Identity() }, { Mat4::Identity() } };
std::unordered_map<GLuint, TextureResource> textures;
GLuint nextTextureId = 1;
UINT nextSrvIndex = 1;
ComPtr<IDXGIFactory4> factory;
ComPtr<IDXGIAdapter3> adapter;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12CommandQueue> queue;
std::string adapterName;
UINT64 dedicatedVideoMemory = 0;
ComPtr<ID3D12DescriptorHeap> srvHeap;
UINT srvStride = 0;
ComPtr<ID3D12GraphicsCommandList> cmdList;
ComPtr<ID3D12Fence> fence;
HANDLE fenceEvent = nullptr;
ComPtr<ID3D12RootSignature> rootSig;
ComPtr<ID3D12RootSignature> postRootSig;
ComPtr<ID3D12PipelineState> postCopyPSO;
ComPtr<ID3D12PipelineState> postSharpenPSO;
ComPtr<ID3D12PipelineState> postToneMapPSO;
ComPtr<ID3D12PipelineState> postToneMapTileMaxPSO;
ComPtr<ID3D12PipelineState> postToneMapFinalMaxPSO;
ComPtr<ID3D12PipelineState> postTaaPSO;
ComPtr<ID3D12PipelineState> postAdditivePSO;
ComPtr<ID3D12PipelineState> postDepthCopyPSO;
ComPtr<ID3D12PipelineState> postDepthResolveMsaaPSO;
ComPtr<ID3D12PipelineState> postGBufferPointResolveMsaaPSO;
ComPtr<ID3D12PipelineState> postScreenDecalPSO;
ComPtr<ID3D12PipelineState> postScreenDecalStainPSO;
ComPtr<ID3D12PipelineState> postScreenParticlePSO;
ComPtr<ID3D12PipelineState> postScreenParticleAdditivePSO;
ComPtr<ID3D12PipelineState> postScreenParticleEmissivePSO;
ComPtr<ID3DBlob> vsMainBlob;
ComPtr<ID3DBlob> hsMainBlob;
ComPtr<ID3DBlob> dsMainBlob;
ComPtr<ID3DBlob> psMainBlob;
ComPtr<ID3DBlob> psAlphaBlob;
ComPtr<ID3DBlob> psUntexturedBlob;
ComPtr<ID3DBlob> psMainColorOnlyBlob;
ComPtr<ID3DBlob> psAlphaColorOnlyBlob;
ComPtr<ID3DBlob> psUntexturedColorOnlyBlob;
ComPtr<ID3DBlob> postVsBlob;
ComPtr<ID3DBlob> postPsCopyBlob;
ComPtr<ID3DBlob> postPsSharpenBlob;
ComPtr<ID3DBlob> postPsToneMapBlob;
ComPtr<ID3DBlob> postPsToneMapTileMaxBlob;
ComPtr<ID3DBlob> postPsToneMapFinalMaxBlob;
ComPtr<ID3DBlob> postPsTaaBlob;
ComPtr<ID3DBlob> postPsAddBlob;
ComPtr<ID3DBlob> postPsDepthCopyBlob;
ComPtr<ID3DBlob> postPsDepthResolveMsaaBlob;
ComPtr<ID3DBlob> postPsGBufferPointResolveMsaaBlob;
ComPtr<ID3DBlob> postPsScreenDecalBlob;
ComPtr<ID3DBlob> postVsScreenParticleBlob;
ComPtr<ID3DBlob> postPsScreenParticleBlob;
TextureResource whiteTexture;
GLenum defaultMinFilter = GL_LINEAR;
GLenum defaultMagFilter = GL_LINEAR;
GLenum defaultWrapS = GL_REPEAT;
GLenum defaultWrapT = GL_REPEAT;
GLState()
{
queries.max_load_factor(0.70f);
buffers.max_load_factor(0.70f);
prevObjectMVPs.max_load_factor(0.70f);
currObjectMVPs.max_load_factor(0.70f);
textures.max_load_factor(0.70f);
queries.reserve(QD3D12_MaxQueries);
queryMarkers.reserve(256);
buffers.reserve(256);
queuedBatches.reserve(4096);
prevObjectMVPs.reserve(1024);
currObjectMVPs.reserve(1024);
textures.reserve(QD3D12_MaxTextures);
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<HWND, QD3D12Window> g_windows;
QD3D12Window* g_currentWindow = nullptr;
static std::unordered_map<uint32_t, uint32_t> g_qd3d12RaytracingMeshMaterialFlags;
struct QD3D12AutoCameraHistory
{
bool haveLastCamera = false;
bool haveFramePrevious = false;
bool haveCachedUpdate = false;
uint64_t frameSerial = UINT64_MAX;
uint64_t cachedFrameSerial = UINT64_MAX;
bool cachedMotionHistoryReset = false;
UINT cachedRenderWidth = 0;
UINT cachedRenderHeight = 0;
float lastViewToClip[16] = {};
float lastWorldToView[16] = {};
float framePreviousViewToClip[16] = {};
float framePreviousWorldToView[16] = {};
float cachedProjection[16] = {};
float cachedModelView[16] = {};
float cachedModelToWorld[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));
}
void FreeD3D12Window(struct QD3D12Window* wnd) {
if (wnd)
{
if ((g_gl.frameOpen && g_gl.frameOwner == wnd) || g_currentWindow == wnd)
QD3D12_SubmitOpenFrameNoPresentAndWait();
if (g_currentWindow == wnd)
g_currentWindow = nullptr;
if (g_gl.device && g_gl.queue && g_gl.fence)
QD3D12_WaitForGPU();
QD3D12_DestroyUploadRingForWindow(*wnd);
QD3D12_UnregisterWindowDC(wnd->hdc);
if (wnd->ownsHdc && wnd->hwnd && wnd->hdc)
ReleaseDC(wnd->hwnd, wnd->hdc);
}
delete wnd;
}
static bool QD3D12_MatrixInvertCM(const float* m, float* out)
{
if (!QD3D12_MatrixFinite(m) || !out)
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;
const UINT renderWidth = g_currentWindow ? g_currentWindow->renderWidth : 0;
const UINT renderHeight = g_currentWindow ? g_currentWindow->renderHeight : 0;
if (g_qd3d12AutoCamera.haveCachedUpdate &&
g_qd3d12AutoCamera.cachedFrameSerial == g_gl.frameSerial &&
g_qd3d12AutoCamera.cachedMotionHistoryReset == g_gl.motionHistoryReset &&
g_qd3d12AutoCamera.cachedRenderWidth == renderWidth &&
g_qd3d12AutoCamera.cachedRenderHeight == renderHeight &&
g_gl.cameraState.valid &&
memcmp(g_qd3d12AutoCamera.cachedProjection, glProjection, sizeof(g_qd3d12AutoCamera.cachedProjection)) == 0 &&
memcmp(g_qd3d12AutoCamera.cachedModelView, modelView, sizeof(g_qd3d12AutoCamera.cachedModelView)) == 0 &&
memcmp(g_qd3d12AutoCamera.cachedModelToWorld, modelToWorld, sizeof(g_qd3d12AutoCamera.cachedModelToWorld)) == 0)
{
return true;
}
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,
worldToView,
viewToWorld,
cameraPos,
cameraRight,
cameraUp,
cameraForward,
nearPlane,
farPlane,
verticalFovRadians,
aspectRatio);
QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastViewToClip, viewToClip);
QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastWorldToView, worldToView);
g_qd3d12AutoCamera.haveLastCamera = true;
g_qd3d12AutoCamera.haveCachedUpdate = true;
g_qd3d12AutoCamera.cachedFrameSerial = g_gl.frameSerial;
g_qd3d12AutoCamera.cachedMotionHistoryReset = g_gl.motionHistoryReset;
g_qd3d12AutoCamera.cachedRenderWidth = renderWidth;
g_qd3d12AutoCamera.cachedRenderHeight = renderHeight;
QD3D12_MatrixCopy(g_qd3d12AutoCamera.cachedProjection, glProjection);
QD3D12_MatrixCopy(g_qd3d12AutoCamera.cachedModelView, modelView);
QD3D12_MatrixCopy(g_qd3d12AutoCamera.cachedModelToWorld, modelToWorld);
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()
{
return QD3D12_CurrentRayMaterialFlags();
}
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 bool QD3D12_GeometryFlagHasGlass(float geometryFlag)
{
const uint32_t bits = (uint32_t)max(0.0f, floorf(geometryFlag + 0.5f));
return (bits & QD3D12_GEOMETRY_FLAG_GLASS_BIT) != 0u;
}
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 constexpr UINT QD3D12_LightingTextureCount = 2;
static GLuint g_lightingTextureId[QD3D12_LightingTextureCount] = {};
static TextureResource* g_lightingTexture[QD3D12_LightingTextureCount] = {};
static D3D12_RESOURCE_STATES g_lightingTextureState[QD3D12_LightingTextureCount] =
{
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COMMON
};
static bool g_lightingTextureValid[QD3D12_LightingTextureCount] = {};
static UINT g_lightingReadIndex = 0;
static UINT g_lightingWriteIndex = 0;
static ComPtr<ID3D12Resource> 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 void QD3D12_ResetPackedVertexBuffer(GLBufferObject& bo)
{
if (bo.packedVertexResource && bo.packedVertexMapped)
bo.packedVertexResource->Unmap(0, nullptr);
bo.packedVertexResource.Reset();
bo.packedVertexMapped = nullptr;
bo.packedVertexGpuAddress = 0;
bo.packedVertexBytes = 0;
bo.packedVertexCount = 0;
bo.packedVertexRevision = 0;
bo.packedVertexLayoutHash = 0;
}
static void QD3D12_ResetBufferResource(GLBufferObject& bo)
{
if (bo.resource && bo.mappedGpu)
bo.resource->Unmap(0, nullptr);
bo.resource.Reset();
bo.mappedGpu = nullptr;
bo.gpuAddress = 0;
bo.gpuBytes = 0;
QD3D12_ResetPackedVertexBuffer(bo);
}
static bool QD3D12_CreateUploadBufferResource(GLBufferObject& bo, size_t size)
{
if (size == 0)
{
QD3D12_ResetBufferResource(bo);
return true;
}
if (!g_gl.device)
return false;
if (bo.resource && bo.gpuBytes >= size && bo.mappedGpu)
return true;
if (bo.resource && bo.mappedGpu)
bo.resource->Unmap(0, nullptr);
bo.resource.Reset();
bo.mappedGpu = nullptr;
bo.gpuAddress = 0;
bo.gpuBytes = 0;
D3D12_HEAP_PROPERTIES hp{};
hp.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC rd{};
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
rd.Width = (UINT64)size;
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_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&bo.resource)));
bo.gpuAddress = bo.resource->GetGPUVirtualAddress();
bo.gpuBytes = size;
QD3D12_CHECK(bo.resource->Map(0, nullptr, reinterpret_cast<void**>(&bo.mappedGpu)));
return bo.mappedGpu != nullptr;
}
static void QD3D12_UpdateBufferResource(GLBufferObject& bo, GLenum target, GLsizeiptr size, const void* data, GLenum usage)
{
bo.target = target;
bo.usage = usage;
bo.storageFlags = 0;
bo.data.resize((size_t)size);
++bo.revision;
QD3D12_ResetPackedVertexBuffer(bo);
if (size > 0)
{
if (data)
memcpy(bo.data.data(), data, (size_t)size);
else
memset(bo.data.data(), 0, (size_t)size);
}
if (QD3D12_CreateUploadBufferResource(bo, (size_t)size) && size > 0)
memcpy(bo.mappedGpu, bo.data.data(), (size_t)size);
}
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;
}
ID3D12Resource* QD3D12_GetTextureResource(GLuint texture, DXGI_FORMAT* format, UINT* width, UINT* height, ID3D12GraphicsCommandList* commandList)
{
TextureResource* tex = QD3D12_FindTextureResource(texture);
if (!tex)
return nullptr;
EnsureTextureResource(*tex);
if (!tex->texture)
return nullptr;
if (!tex->gpuValid && commandList)
UploadTexture(*tex, commandList);
if (!tex->gpuValid)
return nullptr;
if (format)
*format = tex->dxgiFormat;
if (width)
*width = (UINT)tex->width;
if (height)
*height = (UINT)tex->height;
return tex->texture.Get();
}
static TextureResource& QD3D12_EnsureTextureName(GLuint id)
{
auto it = g_gl.textures.find(id);
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<const uint8_t*>(ptr);
}
static void QD3D12_CaptureArrayPointer(GLState::ClientArrayState& array, GLint size, GLenum type, GLsizei stride, const void* pointer)
{
array.size = size;
array.type = type;
array.stride = stride;
array.ptr = QD3D12_ResolveArrayPointer(pointer);
array.buffer = g_gl.boundArrayBuffer;
array.offset = (g_gl.boundArrayBuffer != 0) ? (size_t)pointer : 0;
}
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<ID3D12Resource>& 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;
float4 gCurrentColor;
float4 gVertexColorPad;
};
#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.028)
#define gCameraWorldPos gCameraPomPad.xyz
#define gCameraPomValid gCameraPomPad.w
#define gUseNeuralPOM gNeuralPomPad.x
#define gNeuralPOMDebug gNeuralPomPad.y
#define gUseVertexColor gVertexColorPad.x
// 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_GEOMETRY_FLAG_SKELETAL 1u
#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));
}
float3 QD3D12_DecodeTangentSpaceNormalScaled(float3 encodedNormal, float strengthScale)
{
float3 n = encodedNormal * 2.0 - 1.0;
n.y *= gNormalMapYSign;
n.xy *= max(gNormalMapStrength, 0.0) * saturate(strengthScale);
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;
}
)HLSL"
R"HLSL(
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_GetPomDepthLOD(float2 uv, float lod)
{
float4 nm = gNormalMap.SampleLevel(gSamp2, uv, lod);
float authored = QD3D12_AuthoredPomAlphaWeight(nm.a);
if (authored > 0.5)
return saturate(1.0 - nm.a);
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);
if (gUseTex0 > 0.5)
{
float normalDetailGate = saturate((slope - 0.040) * 8.0);
if (normalDetailGate > 0.001)
{
float mipScale = exp2(lod);
float2 texel = QD3D12_NormalMapTexelSize() * mipScale;
float lumC = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv, lod).rgb);
float lumL = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(texel.x, 0.0), lod).rgb);
float lumR = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(texel.x, 0.0), lod).rgb);
float lumU = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(0.0, texel.y), lod).rgb);
float lumD = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(0.0, texel.y), lod).rgb);
float lumAvg = (lumL + lumR + lumU + lumD) * 0.25;
float localContrast = abs(lumC - lumAvg);
float diffuseWeight = saturate((localContrast - 0.018) * 16.0) * normalDetailGate;
float diffuseHeight = saturate(0.5 + (lumC - lumAvg) * 1.85);
normalHeight = lerp(normalHeight, diffuseHeight, diffuseWeight * 0.28);
}
}
return saturate(1.0 - normalHeight);
}
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);
}
float QD3D12_GetRegularPomFade(VSOut i, float2 baseUv)
{
if (gUseNormalMap < 0.5)
return 0.0;
float confidence = QD3D12_GetPomConfidence(baseUv);
if (confidence <= 0.03)
return 0.0;
float3 n, t, b;
QD3D12_BuildPixelTBN(i, n, t, b);
float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos);
float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n);
float NoV = saturate(dot(n, viewWS));
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.10, 0.26, NoV);
return saturate(confidence * distanceFade * grazingFade);
}
struct QD3D12PomTraceResult
{
float2 uv;
float confidence;
float visibility;
};
// 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;
}
)HLSL"
R"HLSL(
QD3D12PomTraceResult QD3D12_TraceReliefPOM(VSOut i, float2 baseUv)
{
QD3D12PomTraceResult r;
r.uv = baseUv;
r.confidence = 0.0;
r.visibility = 1.0;
if (gUseNormalMap < 0.5)
return r;
if (gAlphaBlendPass > 0.5)
return r;
if (gUseTex0 > 0.5)
{
float baseAlpha = gTex0.SampleLevel(gSamp0, baseUv, 0.0).a;
if (baseAlpha < 0.985)
return r;
}
float minHeight = 0.5;
float maxHeight = 0.5;
float authoredWeight = 0.0;
QD3D12_GetPomHeightStats(baseUv, minHeight, maxHeight, authoredWeight);
float heightRange = maxHeight - minHeight;
float threshold = lerp(0.055, 0.014, authoredWeight);
float confidence = saturate((heightRange - threshold) / max(0.18 - threshold, 0.001));
confidence = confidence * confidence * (3.0 - 2.0 * confidence);
if (confidence <= 0.035)
return r;
float3 n, t, b;
QD3D12_BuildPixelTBN(i, n, t, b);
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.025)
return r;
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 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.055, 0.18, ndotv);
float fade = saturate(confidence * distanceFade * grazingFade * cameraConfidence);
if (fade <= 0.001)
return r;
float2 texel = QD3D12_NormalMapTexelSize();
float2 uvGradX = ddx(baseUv);
float2 uvGradY = ddy(baseUv);
float footprint = max(length(uvGradX / max(texel, float2(1.0e-6, 1.0e-6))), length(uvGradY / max(texel, float2(1.0e-6, 1.0e-6))));
float lod = clamp(log2(max(footprint, 1.0)), 0.0, 5.0);
float depthScale = gParallaxScale * lerp(0.68, 1.18, authoredWeight) * confidence * distanceFade * cameraConfidence;
float vz = max(ndotv, lerp(0.18, 0.10, authoredWeight));
float2 parallaxVector = (viewTS.xy / vz) * depthScale;
float parallaxLen = length(parallaxVector);
float maxParallaxShift = lerp(0.014, 0.046, authoredWeight) * lerp(0.85, 1.18, confidence);
if (parallaxLen > maxParallaxShift && parallaxLen > 1.0e-6)
parallaxVector *= maxParallaxShift / parallaxLen;
float layerCountF = lerp(14.0, 46.0, saturate(1.0 - ndotv));
layerCountF = lerp(10.0, layerCountF, distanceFade);
layerCountF = lerp(layerCountF * 0.70, layerCountF, authoredWeight);
uint layerCount = (uint)clamp(layerCountF + 0.5, 10.0, 48.0);
float invLayerCount = rcp((float)layerCount);
float2 deltaUv = parallaxVector * invLayerCount;
float2 prevUv = baseUv;
float2 uv = baseUv;
float prevRayDepth = 0.0;
float rayDepth = 0.0;
float prevSurfaceDepth = QD3D12_GetPomDepthLOD(baseUv, lod);
float surfaceDepth = prevSurfaceDepth;
[loop]
for (uint layer = 0u; layer < 48u; ++layer)
{
if (layer >= layerCount || rayDepth >= surfaceDepth)
break;
prevUv = uv;
prevRayDepth = rayDepth;
prevSurfaceDepth = surfaceDepth;
uv -= deltaUv;
rayDepth += invLayerCount;
surfaceDepth = QD3D12_GetPomDepthLOD(uv, lod);
}
float after = surfaceDepth - rayDepth;
float before = prevSurfaceDepth - prevRayDepth;
float denom = after - before;
float w = (abs(denom) > 1.0e-5) ? saturate(after / denom) : 0.0;
float2 refinedUv = lerp(uv, prevUv, w);
float2 loUv = uv;
float2 hiUv = prevUv;
[unroll]
for (uint refine = 0u; refine < 5u; ++refine)
{
float2 midUv = (loUv + hiUv) * 0.5;
float midT = dot(baseUv - midUv, parallaxVector) / max(dot(parallaxVector, parallaxVector), 1.0e-8);
float midDepth = saturate(midT);
float midSurface = QD3D12_GetPomDepthLOD(midUv, lod);
if (midDepth < midSurface)
hiUv = midUv;
else
loUv = midUv;
}
refinedUv = lerp(refinedUv, (loUv + hiUv) * 0.5, 0.65);
float2 finalOffset = refinedUv - baseUv;
float maxOffset = maxParallaxShift * 1.05;
float finalLen = length(finalOffset);
if (finalLen > maxOffset && finalLen > 1.0e-6)
refinedUv = baseUv + finalOffset * (maxOffset / finalLen);
r.uv = lerp(baseUv, refinedUv, fade);
r.confidence = fade;
r.visibility = saturate(1.0 - length(r.uv - baseUv) / max(maxParallaxShift, 1.0e-5) * 0.18);
return r;
}
float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPOMResult nr)
{
if (nr.active > 0.5)
return nr.uv;
QD3D12PomTraceResult trace = QD3D12_TraceReliefPOM(i, baseUv);
return trace.uv;
}
)HLSL"
R"HLSL(
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 cinematic HDR radiance in the FP16 emissive G-buffer. The bloom pass
// and particle-light gather both expect a strong source value here so small
// Doom 3 glow sheets survive tone mapping and denoise.
const float kDefaultGlowBloomRadiance = 7.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)
{
float2 specUv = m.uv0;
float4 spec = gSpecularMap.Sample(gSamp4, specUv);
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);
float regularPomFade = (gUseNeuralPOM <= 0.5 || m.neural.active <= 0.5)
? QD3D12_GetRegularPomFade(i, i.uv0)
: 0.0;
float3 tangentBase = QD3D12_DecodeTangentSpaceNormalScaled(gNormalMap.Sample(gSamp2, i.uv0).xyz, 1.0);
float3 tangentPom = QD3D12_DecodeTangentSpaceNormalScaled(gNormalMap.Sample(gSamp2, m.uv0).xyz, lerp(0.70, 1.0, regularPomFade));
float3 tangentNormal = QD3D12_SafeNormalize(lerp(tangentBase, tangentPom, regularPomFade), tangentBase);
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);
prevClip.z = 0.5 * (prevClip.z + prevClip.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 = (gUseVertexColor > 0.5) ? i.col : gCurrentColor;
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_TessellationPatchEdgeFade(float3 bary)
{
// Keep original patch edges on the undisplaced mesh. Brush seams and room
// corners often meet only at those edges; moving both sides along different
// normals can open real gaps that show the background.
float edgeDistance = min(bary.x, min(bary.y, bary.z));
return smoothstep(0.0, 0.08, edgeDistance);
}
uint QD3D12_DecodeGeometryFlag(float flag)
{
return (uint)floor(max(flag, 0.0) + 0.5);
}
bool QD3D12_IsSkeletalGeometry(float flag)
{
return (QD3D12_DecodeGeometryFlag(flag) & QD3D12_GEOMETRY_FLAG_SKELETAL) != 0u;
}
float QD3D12_ComputeCharacterTessEdgeFactor(VSOut a, VSOut b)
{
float distanceFade = QD3D12_TessellationEdgeDistanceFade(a, b);
if (distanceFade <= 0.001)
return 1.0;
float edgePixels = QD3D12_EdgeLengthPixels(a.currClip, b.currClip);
float screenTerm = sqrt(max(edgePixels, 1.0) / 24.0);
float strength = clamp(max(gNormalMapStrength, 0.0), 0.0, 4.0);
// Character tessellation is for silhouette/deformation smoothness, not
// normal-map relief. Keep it bounded so animated MD5s do not turn rubbery.
float nearFactor = 1.0 + screenTerm * 2.65 + strength * 0.22;
nearFactor = clamp(nearFactor, 1.0, 8.0);
return clamp(lerp(1.0, nearFactor, distanceFade), QD3D12_TESS_MIN_FACTOR, 8.0);
}
float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b)
{
if (gUseNormalMap <= 0.5)
return 1.0;
if (QD3D12_IsSkeletalGeometry(a.attr.x) || QD3D12_IsSkeletalGeometry(b.attr.x))
return QD3D12_ComputeCharacterTessEdgeFactor(a, b);
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<VSOut, 3> 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<VSOut, 3> 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;
}
float3 QD3D12_ProjectPointToTangentPlane(float3 p, float3 planePoint, float3 planeNormal)
{
planeNormal = QD3D12_SafeNormalize(planeNormal, float3(0.0, 0.0, 1.0));
return p - planeNormal * dot(p - planePoint, planeNormal);
}
float3 QD3D12_CharacterPhongTessellate(const OutputPatch<TessCP, 3> patch, float3 bary, float3 linearObjPos, float distanceFade)
{
float3 n0 = QD3D12_SafeNormalize(patch[0].objNormal, float3(0.0, 0.0, 1.0));
float3 n1 = QD3D12_SafeNormalize(patch[1].objNormal, n0);
float3 n2 = QD3D12_SafeNormalize(patch[2].objNormal, n0);
float3 q0 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[0].objPos, n0);
float3 q1 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[1].objPos, n1);
float3 q2 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[2].objPos, n2);
float3 phongObjPos = q0 * bary.x + q1 * bary.y + q2 * bary.z;
float normalAgreement = saturate((dot(n0, n1) + dot(n1, n2) + dot(n2, n0)) * 0.1667 + 0.5);
float smoothAmount = 0.78 * distanceFade * smoothstep(0.10, 0.82, normalAgreement);
float3 delta = phongObjPos - linearObjPos;
float maxEdgeLen = max(
length(patch[0].objPos - patch[1].objPos),
max(length(patch[1].objPos - patch[2].objPos), length(patch[2].objPos - patch[0].objPos)));
float maxDelta = max(maxEdgeLen * 0.075, 0.01);
float deltaLen = length(delta);
if (deltaLen > maxDelta)
delta *= maxDelta / max(deltaLen, 1.0e-5);
return linearObjPos + delta * smoothAmount;
}
[domain("tri")]
VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const OutputPatch<TessCP, 3> 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));
bool isSkeletal = QD3D12_IsSkeletalGeometry(i.attr.x);
float distanceFade = QD3D12_TessellationDisplacementFade(i.currClip);
float3 baseObjPos = isSkeletal
? QD3D12_CharacterPhongTessellate(patch, bary, i.objPos, distanceFade)
: i.objPos;
float height = QD3D12_GetFilteredTessHeight(i.uv0);
float centeredHeight = QD3D12_CleanCenteredTessHeight(height);
float displacementFade = isSkeletal ? 0.0 : distanceFade * QD3D12_TessellationPatchEdgeFade(bary);
float reliefConfidence = QD3D12_GetPomConfidence(i.uv0);
float displacement = centeredHeight * gTessellationDisplacement * displacementFade * reliefConfidence;
float3 displacedObjPos = baseObjPos + 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);
prevClip.z = 0.5 * (prevClip.z + prevClip.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<float> gDepthMS : register(t1);
Texture2DMS<float4> gNormalMS : register(t2);
Texture2DMS<float4> gPositionMS : register(t3);
Texture2DMS<float4> gVelocityMS : register(t4);
Texture2DMS<float4> gEmissiveMS : register(t5);
Texture2DMS<float4> gSpecularMS : register(t6);
Texture2D gTaaHistory : register(t7);
Texture2D gTaaVelocity : register(t8);
Texture2D<float4> gFogPosition : register(t9);
Texture2D<float> gFogDepth : register(t10);
Texture2D<float4> gScreenDecalNormal : register(t11);
Texture2D<float4> gScreenDecalPosition : register(t12);
SamplerState gSamp0 : register(s0);
struct ScreenSpaceDecal
{
float3 origin;
float halfSize;
float3 normal;
float depth;
float3 axisU;
float opacity;
float3 axisV;
float pad0;
float4 color;
uint textureId;
uint blendMode;
float2 pad1;
};
StructuredBuffer<ScreenSpaceDecal> gScreenDecals : register(t13);
struct ScreenParticleLight
{
float3 position;
float radius;
float3 color;
float intensity;
float3 normal;
uint type;
float3 axisU;
float halfWidth;
float3 axisV;
float halfHeight;
uint samples;
uint twoSided;
float persistant;
float volumetricScattering;
float3 pointRadius;
float pointRadiusPad;
uint iesTextureId;
float iesStrength;
float2 iesPad;
};
StructuredBuffer<ScreenParticleLight> gScreenParticleLights : register(t14);
#if defined(QD3D12_SCREEN_PARTICLE_RAYQUERY)
RaytracingAccelerationStructure gScreenParticleSceneBVH : register(t15);
#endif
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, z = particle soft depth, w = particle light count.
float4 gPostParams0;
float4 gPostParams1;
float4 gPostParams2;
float4 gPostParams3;
float4 gPostFogColor;
float4 gPostFogCameraWorldPos;
float4 gPostParticleParams;
};
#define gPostSharpness gPostParams0.x
#define gTaaHistoryWeight gPostParams0.y
#define gTaaReset gPostParams0.z
#define gScreenDecalIndex gPostParams0.w
#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 gScreenParticleSoftDepth gPostParams3.z
#define gScreenParticleLightCount gPostParams3.w
#define gScreenDecalCount gPostFogCameraWorldPos.w
#define gScreenParticleEmissiveScale gPostParticleParams.x
#define gScreenParticleOutputMode gPostParticleParams.y
#define QD3D12_TONEMAP_TILE_DIM 64u
static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0u;
static const uint GL_RAYTRACING_LIGHT_TYPE_RECT = 1u;
static const uint GL_RAYTRACING_LIGHT_TYPE_SPOT = 2u;
struct VSOut
{
float4 pos : SV_Position;
float2 uv : TEXCOORD0;
};
struct ParticleVSIn
{
float4 clipPos : POSITION0;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
float3 world : TEXCOORD1;
};
struct ParticleVSOut
{
float4 pos : SV_Position;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
float3 world : TEXCOORD1;
};
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;
}
ParticleVSOut VSParticle(ParticleVSIn i)
{
ParticleVSOut o;
o.pos = i.clipPos;
o.uv = i.uv;
o.color = i.color;
o.world = i.world;
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;
}
float3 QD3D12_SafeNormalizeOr(float3 v, float3 fallbackDir)
{
float lenSq = dot(v, v);
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir;
}
float4 PSScreenDecals(VSOut i) : SV_Target0
{
uint2 pixel = uint2(i.pos.xy);
float4 positionSample = gScreenDecalPosition.Load(int3(pixel, 0));
uint geometryBits = (uint)round(max(positionSample.w, 0.0));
if ((geometryBits & 1u) != 0u)
return float4(0.0, 0.0, 0.0, 0.0);
float3 worldPos = positionSample.xyz;
float3 decalColor = 0.0;
float decalAlpha = 0.0;
uint decalIndex = (uint)gScreenDecalIndex;
if (decalIndex >= min((uint)gScreenDecalCount, 64u))
return float4(0.0, 0.0, 0.0, 0.0);
ScreenSpaceDecal d = gScreenDecals[decalIndex];
float3 decalN = QD3D12_SafeNormalizeOr(d.normal, float3(0.0, 0.0, 1.0));
float3 rel = worldPos - d.origin;
float projectorDepth = max(d.depth, 1.0e-3);
float projectorZ = dot(rel, decalN);
if (abs(projectorZ) > projectorDepth)
return float4(0.0, 0.0, 0.0, 0.0);
float3 axisU = QD3D12_SafeNormalizeOr(d.axisU, float3(1.0, 0.0, 0.0));
float3 axisV = QD3D12_SafeNormalizeOr(d.axisV, cross(decalN, axisU));
float2 uv = float2(dot(rel, axisU), dot(rel, axisV)) / max(d.halfSize, 1.0e-3);
if (abs(uv.x) > 1.0 || abs(uv.y) > 1.0)
return float4(0.0, 0.0, 0.0, 0.0);
float2 texUv = float2(uv.x * 0.5 + 0.5, -uv.y * 0.5 + 0.5);
float4 texel = gTex0.Sample(gSamp0, texUv);
float mask = saturate(d.opacity) * saturate(d.color.a) * saturate(texel.a);
if (d.blendMode == 1u)
{
decalColor = saturate(texel.rgb * mask);
decalAlpha = 1.0;
}
else
{
decalColor = texel.rgb * saturate(d.color.rgb);
decalAlpha = mask;
}
return float4(decalColor, decalAlpha);
}
float3 ScreenParticleSafeNormalizeOr(float3 v, float3 fallbackDir)
{
float lenSq = dot(v, v);
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir;
}
float3 ScreenParticlePointRadius(ScreenParticleLight Lgt)
{
float scalarRadius = max(abs(Lgt.radius), 1e-4);
float3 r = abs(Lgt.pointRadius);
if (max(max(r.x, r.y), r.z) <= 1e-4)
r = float3(scalarRadius, scalarRadius, scalarRadius);
return max(r, float3(1e-4, 1e-4, 1e-4));
}
float ScreenParticlePointAttenuation(float3 worldPos, ScreenParticleLight Lgt)
{
float3 radii = ScreenParticlePointRadius(Lgt);
float3 offset = worldPos - Lgt.position;
float3 axisU = ScreenParticleSafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
float3 axisV = ScreenParticleSafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
float3 axisW = ScreenParticleSafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
float3 perAxis;
perAxis.x = 1.0 - saturate(abs(dot(offset, axisU)) / radii.x);
perAxis.y = 1.0 - saturate(abs(dot(offset, axisV)) / radii.y);
perAxis.z = 1.0 - saturate(abs(dot(offset, axisW)) / radii.z);
return perAxis.x * perAxis.y * perAxis.z;
}
float ScreenParticleBasicFalloff(float lightDist, float maxLightDist)
{
return saturate(1.0 - lightDist / max(maxLightDist, 1e-4));
}
float ScreenParticleSpotAttenuation(float3 worldPos, ScreenParticleLight Lgt)
{
float3 lightToSurface = worldPos - Lgt.position;
float nearClip = max(Lgt.pointRadius.x, 0.0);
float farClip = max(Lgt.radius, nearClip + 1e-4);
float3 spotDir = ScreenParticleSafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
float depth = dot(lightToSurface, spotDir);
if (depth <= nearClip || depth >= farClip)
return 0.0;
float3 axisU = ScreenParticleSafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
float3 axisV = ScreenParticleSafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
float invDepth = 1.0 / max(depth, 1e-4);
float halfU = max(abs(Lgt.halfWidth), 1e-4);
float halfV = max(abs(Lgt.halfHeight), 1e-4);
float signedU = (dot(lightToSurface, axisU) * invDepth) / halfU;
float signedV = (dot(lightToSurface, axisV) * invDepth) / halfV;
if (abs(signedU) >= 1.0 || abs(signedV) >= 1.0)
return 0.0;
float3 lightVector = normalize(lightToSurface);
float maxSlope = max(halfU, halfV);
float outerAngle = rsqrt(1.0 + maxSlope * maxSlope);
float innerSlope = maxSlope * 0.65;
float innerAngle = rsqrt(1.0 + innerSlope * innerSlope);
float spotCookie = smoothstep(outerAngle, innerAngle, saturate(dot(lightVector, spotDir)));
return spotCookie * ScreenParticleBasicFalloff(depth - nearClip, farClip - nearClip);
}
float ScreenParticleTraceShadow(float3 worldPos, float3 viewDir, float3 L, float dist, ScreenParticleLight Lgt)
{
if (Lgt.samples == 0u)
return 1.0;
#if defined(QD3D12_SCREEN_PARTICLE_RAYQUERY)
RayDesc ray;
ray.Origin = worldPos + L * 5.0 + viewDir * 1.0;
ray.Direction = L;
ray.TMin = 0.001;
ray.TMax = max(dist - 8.0, 0.001);
RayQuery<RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> q;
q.TraceRayInline(
gScreenParticleSceneBVH,
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES,
0xFF,
ray);
while (q.Proceed()) {}
return (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) ? 0.0 : 1.0;
#else
return 1.0;
#endif
}
float3 ScreenParticleDirectLighting(float3 worldPos)
{
uint lightCount = min((uint)gScreenParticleLightCount, 256u);
if (lightCount == 0u || gPostFogCameraValid <= 0.5)
return 1.0;
float3 viewDir = ScreenParticleSafeNormalizeOr(gPostFogCameraWorldPos.xyz - worldPos, float3(0.0, 0.0, 1.0));
float3 lighting = float3(0.16, 0.16, 0.16);
[loop]
for (uint lightIndex = 0u; lightIndex < lightCount; ++lightIndex)
{
ScreenParticleLight Lgt = gScreenParticleLights[lightIndex];
if (Lgt.type != GL_RAYTRACING_LIGHT_TYPE_POINT && Lgt.type != GL_RAYTRACING_LIGHT_TYPE_SPOT)
continue;
float3 toLight = Lgt.position - worldPos;
float dist = length(toLight);
if (dist <= 0.01)
continue;
float atten = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
? ScreenParticlePointAttenuation(worldPos, Lgt)
: ScreenParticleSpotAttenuation(worldPos, Lgt);
if (atten <= 0.0)
continue;
float3 L = toLight / dist;
float shadow = ScreenParticleTraceShadow(worldPos, viewDir, L, dist, Lgt);
if (shadow <= 0.0)
continue;
float forwardScatter = saturate(dot(viewDir, -L) * 0.5 + 0.5);
float phase = lerp(0.65, 1.0, forwardScatter);
lighting += Lgt.color * (Lgt.intensity * atten * shadow * phase * 0.25);
}
return clamp(lighting, 0.08, 8.0);
}
float4 PSScreenParticles(ParticleVSOut i) : SV_Target0
{
float4 texel = gTex0.Sample(gSamp0, i.uv) * i.color;
float alpha = saturate(texel.a);
if (alpha <= 0.0001)
discard;
uint2 pixel = uint2(i.pos.xy);
float sceneDepth = gFogDepth.Load(int3(pixel, 0));
float particleDepth = saturate(i.pos.z);
if (sceneDepth < 0.99999)
{
if (particleDepth > sceneDepth + 0.00005)
{
discard;
}
if (gPostFogCameraValid > 0.5)
{
float4 positionSample = gScreenDecalPosition.Load(int3(pixel, 0));
uint geometryBits = (uint)round(max(positionSample.w, 0.0));
if ((geometryBits & 1u) == 0u)
{
float sceneDistance = length(positionSample.xyz - gPostFogCameraWorldPos.xyz);
float particleDistance = length(i.world - gPostFogCameraWorldPos.xyz);
float softDepth = max(gScreenParticleSoftDepth, 0.001);
alpha *= saturate((sceneDistance - particleDistance) / softDepth);
if (alpha <= 0.0001)
discard;
}
}
}
if (gScreenParticleOutputMode > 0.5)
{
const float kScreenParticleEmissionBoost = 3.0;
float emissiveAlpha = alpha;
return float4(texel.rgb * (emissiveAlpha * max(gScreenParticleEmissiveScale, 0.0) * kScreenParticleEmissionBoost), 2.0 + particleDepth);
}
texel.rgb *= ScreenParticleDirectLighting(i.world);
return float4(texel.rgb, alpha);
}
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);
}
)HLSL"
R"HLSL(
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)
{
// Keep bloom HDR enough to look cinematic, but use a soft lift instead of an
// unbounded add so one malformed texel cannot bleach the whole frame.
float3 radiance = min(max(gTex0.Sample(gSamp0, saturate(uv)).rgb, 0.0), 32.0);
return min(radiance + sqrt(radiance) * 0.45, 40.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));
// Wide threshold-free bloom kernel. Direct emissive is still added by PSAdd;
// these outer taps make fire and plasma read as real camera bloom instead of
// a thin one-pixel glow.
float3 bloom = 0.0;
bloom += QD3D12_LoadPostRadiance(uv) * 0.320;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 1.0, 0.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-1.0, 0.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 1.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -1.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, 2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, 2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, -2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, -2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 4.0, 0.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-4.0, 0.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 4.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -4.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 8.0, 0.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-8.0, 0.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 8.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -8.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 14.0, 0.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-14.0, 0.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 14.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -14.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 24.0, 0.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-24.0, 0.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 24.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -24.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 42.0, 0.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-42.0, 0.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 42.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -42.0)) * 0.025;
return min(bloom * 1.15, 24.0);
}
)HLSL"
R"HLSL(
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 * 1.08 + 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;
}
)HLSL"
R"HLSL(
struct PSGBufferPointResolveOut
{
float4 normal : SV_Target0;
float4 position : SV_Target1;
float4 velocity : SV_Target2;
float4 emissive : SV_Target3;
float4 specular : SV_Target4;
};
)HLSL"
R"HLSL(
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 = g_gl.curColor[0]; out.g = g_gl.curColor[1]; out.b = g_gl.curColor[2]; out.a = g_gl.curColor[3];
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)
{
if (va.type == GL_FLOAT)
{
const size_t stride = va.stride ? (size_t)va.stride : (size_t)va.size * sizeof(float);
const float* f = (const float*)(va.ptr + stride * (size_t)idx);
if (va.size > 0) out.px = f[0];
if (va.size > 1) out.py = f[1];
if (va.size > 2) out.pz = f[2];
}
else if (va.type == GL_DOUBLE)
{
const size_t stride = va.stride ? (size_t)va.stride : (size_t)va.size * sizeof(double);
const double* f = (const double*)(va.ptr + stride * (size_t)idx);
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];
}
else
{
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;
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);
}
}
//
// Normal
//
const auto& na = g_gl.normalArray;
if (na.enabled && na.ptr)
{
if (na.type == GL_FLOAT)
{
const size_t stride = na.stride ? (size_t)na.stride : 3 * sizeof(float);
const float* f = (const float*)(na.ptr + stride * (size_t)idx);
out.nx = f[0];
out.ny = f[1];
out.nz = f[2];
}
else if (na.type == GL_DOUBLE)
{
const size_t stride = na.stride ? (size_t)na.stride : 3 * sizeof(double);
const double* f = (const double*)(na.ptr + stride * (size_t)idx);
out.nx = (float)f[0];
out.ny = (float)f[1];
out.nz = (float)f[2];
}
else
{
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;
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);
}
}
//
// Tangent
//
const auto& ta = g_gl.tangentArray;
if (ta.enabled && ta.ptr)
{
if (ta.type == GL_FLOAT)
{
const size_t stride = ta.stride ? (size_t)ta.stride : 3 * sizeof(float);
const float* f = (const float*)(ta.ptr + stride * (size_t)idx);
out.tx = f[0];
out.ty = f[1];
out.tz = f[2];
}
else if (ta.type == GL_DOUBLE)
{
const size_t stride = ta.stride ? (size_t)ta.stride : 3 * sizeof(double);
const double* f = (const double*)(ta.ptr + stride * (size_t)idx);
out.tx = (float)f[0];
out.ty = (float)f[1];
out.tz = (float)f[2];
}
else
{
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;
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);
}
}
//
// Bitangent
//
const auto& ba = g_gl.bitangentArray;
if (ba.enabled && ba.ptr)
{
if (ba.type == GL_FLOAT)
{
const size_t stride = ba.stride ? (size_t)ba.stride : 3 * sizeof(float);
const float* f = (const float*)(ba.ptr + stride * (size_t)idx);
out.bx = f[0];
out.by = f[1];
out.bz = f[2];
}
else if (ba.type == GL_DOUBLE)
{
const size_t stride = ba.stride ? (size_t)ba.stride : 3 * sizeof(double);
const double* f = (const double*)(ba.ptr + stride * (size_t)idx);
out.bx = (float)f[0];
out.by = (float)f[1];
out.bz = (float)f[2];
}
else
{
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;
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);
}
}
//
// Color
//
const auto& ca = g_gl.colorArray;
if (ca.enabled && ca.ptr)
{
if (ca.type == GL_UNSIGNED_BYTE)
{
static const float kInv255 = 1.0f / 255.0f;
const size_t stride = ca.stride ? (size_t)ca.stride : (size_t)ca.size * sizeof(GLubyte);
const uint8_t* p = ca.ptr + stride * (size_t)idx;
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 size_t stride = ca.stride ? (size_t)ca.stride : (size_t)ca.size * sizeof(float);
const float* f = (const float*)(ca.ptr + stride * (size_t)idx);
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 size_t stride = ca.stride ? (size_t)ca.stride : (size_t)ca.size * sizeof(double);
const double* f = (const double*)(ca.ptr + stride * (size_t)idx);
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
{
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.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)
{
if (tc.type == GL_FLOAT)
{
const size_t stride = tc.stride ? (size_t)tc.stride : (size_t)tc.size * sizeof(float);
const float* f = (const float*)(tc.ptr + stride * (size_t)idx);
if (tc.size > 0) out.u0 = f[0];
if (tc.size > 1) out.v0 = f[1];
}
else if (tc.type == GL_DOUBLE)
{
const size_t stride = tc.stride ? (size_t)tc.stride : (size_t)tc.size * sizeof(double);
const double* f = (const double*)(tc.ptr + stride * (size_t)idx);
if (tc.size > 0) out.u0 = (float)f[0];
if (tc.size > 1) out.v0 = (float)f[1];
}
else
{
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;
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);
}
}
}
//
// Texcoord 1
//
{
const auto& tc = g_gl.texCoordArray[1];
if (tc.enabled && tc.ptr)
{
if (tc.type == GL_FLOAT)
{
const size_t stride = tc.stride ? (size_t)tc.stride : (size_t)tc.size * sizeof(float);
const float* f = (const float*)(tc.ptr + stride * (size_t)idx);
if (tc.size > 0) out.u1 = f[0];
if (tc.size > 1) out.v1 = f[1];
}
else if (tc.type == GL_DOUBLE)
{
const size_t stride = tc.stride ? (size_t)tc.stride : (size_t)tc.size * sizeof(double);
const double* f = (const double*)(tc.ptr + stride * (size_t)idx);
if (tc.size > 0) out.u1 = (float)f[0];
if (tc.size > 1) out.v1 = (float)f[1];
}
else
{
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;
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);
}
}
}
}
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<GLint>(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;
}
}
static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource* tex0, const TextureResource* tex1, TextureResource* const* allTextures,
TextureResource* selectedNormalMap = nullptr, TextureResource* selectedGlowMap = nullptr, TextureResource* selectedSpecularMap = nullptr, bool selectedMapsValid = false,
bool finalizeHash = true)
{
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 = selectedMapsValid ? selectedNormalMap : 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 = selectedMapsValid ? selectedGlowMap : 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 = selectedMapsValid ? selectedSpecularMap : 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();
key.arbConstants = std::make_shared<QD3D12ARBDrawConstantArrays>();
QD3D12ARB_FillDrawConstantArrays(key.arbConstants.get());
if (!key.arbVertexBlob || !key.arbFragmentBlob)
{
key.useARBPrograms = false;
key.arbConstants.reset();
}
}
key.alphaRef = g_gl.alphaRef;
key.alphaFunc = g_gl.alphaFuncMapped;
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];
key.currentColor[0] = g_gl.curColor[0];
key.currentColor[1] = g_gl.curColor[1];
key.currentColor[2] = g_gl.curColor[2];
key.currentColor[3] = g_gl.curColor[3];
key.useVertexColor = g_gl.colorArray.enabled ? 1.0f : 0.0f;
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;
if (finalizeHash)
QD3D12_FinalizeBatchKeyHash(key);
return key;
}
static void QD3D12_MixTextureStamp(uint64_t& h, const TextureResource* tex)
{
QD3D12_MixHash(h, reinterpret_cast<uintptr_t>(tex));
if (!tex)
return;
QD3D12_MixHash(h, tex->glId);
QD3D12_MixHash(h, tex->srvIndex);
QD3D12_MixHash(h, tex->gpuValid ? 1ull : 0ull);
QD3D12_MixHash(h, tex->isNormalMap ? 1ull : 0ull);
QD3D12_MixHash(h, tex->isGlowMap ? 1ull : 0ull);
QD3D12_MixHash(h, tex->isSpecularMap ? 1ull : 0ull);
QD3D12_MixHash(h, tex->cpuGeneration);
QD3D12_MixHash(h, tex->neuralPOM.gpuValid ? 1ull : 0ull);
QD3D12_MixHash(h, tex->neuralPOM.gpuGeneration);
QD3D12_MixHash(h, tex->neuralPOM.weightsSrvIndex);
QD3D12_MixHash(h, tex->neuralPOM.latentSrvIndex);
}
static uint64_t QD3D12_CurrentImmediateBatchStateStamp(
GLenum originalMode,
TextureResource* const* allTextures,
TextureResource* selectedNormalMap,
TextureResource* selectedGlowMap,
TextureResource* selectedSpecularMap,
bool tessellationVertexCountOk)
{
if (QD3D12ARB_IsActive() || !g_currentWindow || g_gl.modelStack.empty() || g_gl.projStack.empty())
return 0;
uint64_t h = 1469598103934665603ull;
QD3D12_MixHash(h, originalMode);
QD3D12_MixHash(h, tessellationVertexCountOk ? 1ull : 0ull);
QD3D12_MixHash(h, reinterpret_cast<uintptr_t>(g_currentWindow));
QD3D12_MixHashBytes(h, &g_currentWindow->viewport, sizeof(g_currentWindow->viewport));
QD3D12_MixHashBytes(h, &g_currentWindow->scissor, sizeof(g_currentWindow->scissor));
QD3D12_MixHash(h, g_currentWindow->renderWidth);
QD3D12_MixHash(h, g_currentWindow->renderHeight);
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
{
QD3D12_MixHash(h, g_gl.boundTexture[unit]);
QD3D12_MixHash(h, g_gl.texture2D[unit] ? 1ull : 0ull);
QD3D12_MixTextureStamp(h, allTextures ? allTextures[unit] : nullptr);
}
QD3D12_MixTextureStamp(h, selectedNormalMap);
QD3D12_MixTextureStamp(h, selectedGlowMap);
QD3D12_MixTextureStamp(h, selectedSpecularMap);
QD3D12_MixHashFloat(h, g_gl.alphaRef);
QD3D12_MixHashFloat(h, g_gl.alphaFuncMapped);
QD3D12_MixHash(h, g_gl.alphaTest ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.blend ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(g_gl.blendSrc));
QD3D12_MixHash(h, uint32_t(g_gl.blendDst));
QD3D12_MixHash(h, g_gl.depthTest ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.depthWrite ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(g_gl.depthFunc));
QD3D12_MixHash(h, g_gl.cullFace ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(g_gl.cullMode));
QD3D12_MixHash(h, uint32_t(g_gl.frontFace));
QD3D12_MixHash(h, QD3D12_CurrentColorWriteMask());
QD3D12_MixHash(h, g_gl.fog ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(g_gl.fogMode));
QD3D12_MixHashFloat(h, g_gl.fogDensity);
QD3D12_MixHashFloat(h, g_gl.fogStart);
QD3D12_MixHashFloat(h, g_gl.fogEnd);
QD3D12_MixHashBytes(h, g_gl.fogColor, sizeof(g_gl.fogColor));
QD3D12_MixHashBytes(h, g_gl.curColor, sizeof(g_gl.curColor));
QD3D12_MixHash(h, g_gl.colorArray.enabled ? 1ull : 0ull);
QD3D12_MixHash(h, uint32_t(g_gl.texEnvMode[0]));
QD3D12_MixHash(h, uint32_t(g_gl.texEnvMode[1]));
for (UINT unit = 0; unit < 2; ++unit)
{
QD3D12_MixHash(h, uint32_t(g_gl.texCombineRGB[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texCombineAlpha[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texSource0RGB[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texSource1RGB[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texSource0Alpha[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texSource1Alpha[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texOperand0RGB[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texOperand1RGB[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texOperand0Alpha[unit]));
QD3D12_MixHash(h, uint32_t(g_gl.texOperand1Alpha[unit]));
QD3D12_MixHashFloat(h, g_gl.texRGBScale[unit]);
QD3D12_MixHashFloat(h, g_gl.texAlphaScale[unit]);
QD3D12_MixHashBytes(h, g_gl.texEnvColor[unit], sizeof(g_gl.texEnvColor[unit]));
}
QD3D12_MixHash(h, g_gl.stencilTest ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.stencilFrontMask);
QD3D12_MixHash(h, g_gl.stencilBackMask);
QD3D12_MixHash(h, g_gl.stencilFrontFuncMask);
QD3D12_MixHash(h, g_gl.stencilBackFuncMask);
QD3D12_MixHash(h, uint32_t(g_gl.stencilFrontFunc));
QD3D12_MixHash(h, uint32_t(g_gl.stencilFrontSFail));
QD3D12_MixHash(h, uint32_t(g_gl.stencilFrontDPFail));
QD3D12_MixHash(h, uint32_t(g_gl.stencilFrontDPPass));
QD3D12_MixHash(h, uint32_t(g_gl.stencilBackFunc));
QD3D12_MixHash(h, uint32_t(g_gl.stencilBackSFail));
QD3D12_MixHash(h, uint32_t(g_gl.stencilBackDPFail));
QD3D12_MixHash(h, uint32_t(g_gl.stencilBackDPPass));
QD3D12_MixHash(h, g_gl.stencilFrontRef);
QD3D12_MixHash(h, g_gl.stencilBackRef);
QD3D12_MixHash(h, g_gl.depthBoundsTest ? 1ull : 0ull);
QD3D12_MixHashFloat(h, (float)g_gl.depthBoundsMin);
QD3D12_MixHashFloat(h, (float)g_gl.depthBoundsMax);
QD3D12_MixHash(h, g_gl.polygonOffsetPoint ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.polygonOffsetLine ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.polygonOffsetFill ? 1ull : 0ull);
QD3D12_MixHashFloat(h, g_gl.polygonOffsetFactor);
QD3D12_MixHashFloat(h, g_gl.polygonOffsetUnits);
QD3D12_MixHashBytes(h, g_gl.projStack.back().m, sizeof(g_gl.projStack.back().m));
QD3D12_MixHashBytes(h, g_gl.modelStack.back().m, sizeof(g_gl.modelStack.back().m));
QD3D12_MixHashBytes(h, g_gl.modelMatrix.m, sizeof(g_gl.modelMatrix.m));
QD3D12_MixHash(h, g_gl.currentMotionObjectId);
QD3D12_MixHashFloat(h, g_gl.currentGeometryFlag);
QD3D12_MixHashFloat(h, g_gl.currentSurfaceRoughness);
QD3D12_MixHashFloat(h, g_gl.currentMaterialType);
QD3D12_MixHash(h, g_gl.currentRayMaterialFlags);
QD3D12_MixHash(h, g_gl.frameSerial);
QD3D12_MixHash(h, g_gl.motionHistoryReset ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.currentNormalMapTexture);
QD3D12_MixHashFloat(h, g_gl.currentNormalMapStrength);
QD3D12_MixHashFloat(h, g_gl.currentNormalMapYSign);
QD3D12_MixHash(h, g_gl.currentNeuralPOMTexture);
QD3D12_MixHash(h, g_gl.neuralPOMEnabled ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.currentGlowMapTexture);
QD3D12_MixHashFloat(h, g_gl.currentGlowMapStrength);
QD3D12_MixHash(h, g_gl.currentSpecularMapTexture);
QD3D12_MixHashFloat(h, g_gl.currentSpecularMapStrength);
return h ? h : 1ull;
}
static BatchKey QD3D12_GetOrBuildImmediateBatchKey(
GLenum mode,
size_t vertexCount,
TextureResource* const* boundTextures,
TextureResource* normalMapTex,
TextureResource* glowMapTex,
TextureResource* specularMapTex)
{
const bool tessellationVertexCountOk = QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(mode, vertexCount);
const uint64_t stateStamp = QD3D12_CurrentImmediateBatchStateStamp(
mode,
boundTextures,
normalMapTex,
glowMapTex,
specularMapTex,
tessellationVertexCountOk);
if (stateStamp != 0 &&
g_gl.cachedImmediateBatchKeyValid &&
g_gl.cachedImmediateBatchStamp == stateStamp)
{
BatchKey key = g_gl.cachedImmediateBatchKey;
g_gl.currObjectMVPs[key.motionObjectId] = key.mvp;
return key;
}
BatchKey key = BuildCurrentBatchKey(mode, boundTextures[0], boundTextures[1], boundTextures, normalMapTex, glowMapTex, specularMapTex, true, stateStamp == 0);
if (key.useTessellation && !tessellationVertexCountOk)
{
key.useTessellation = false;
if (stateStamp == 0)
QD3D12_FinalizeBatchKeyHash(key);
}
if (stateStamp != 0)
{
key.fullHash = stateStamp;
g_gl.cachedImmediateBatchKey = key;
g_gl.cachedImmediateBatchStamp = stateStamp;
g_gl.cachedImmediateBatchKeyValid = true;
}
else
{
g_gl.cachedImmediateBatchKeyValid = false;
}
return key;
}
static uint64_t QD3D12_GatherImmediateTextureCandidates(bool arbProgramsActive, TextureResource** boundTextures)
{
uint64_t h = 1469598103934665603ull;
QD3D12_MixHash(h, arbProgramsActive ? 1ull : 0ull);
QD3D12_MixHash(h, g_gl.currentNormalMapTexture);
QD3D12_MixHash(h, g_gl.currentGlowMapTexture);
QD3D12_MixHash(h, g_gl.currentSpecularMapTexture);
QD3D12_MixHash(h, g_gl.currentNeuralPOMTexture);
QD3D12_MixHash(h, g_gl.neuralPOMEnabled ? 1ull : 0ull);
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
{
boundTextures[unit] = &g_gl.whiteTexture;
const GLuint boundTexture = g_gl.boundTexture[unit];
QD3D12_MixHash(h, boundTexture);
QD3D12_MixHash(h, g_gl.texture2D[unit] ? 1ull : 0ull);
TextureResource* tex = boundTexture != 0 ? QD3D12_FindTextureResource(boundTexture) : nullptr;
QD3D12_MixTextureStamp(h, tex);
const bool fixedColorUnit = (!arbProgramsActive) && (g_gl.texture2D[unit] && unit < 2);
const bool taggedNormalUnit = (!arbProgramsActive) && tex && tex->isNormalMap;
const bool explicitNormalUnit = (!arbProgramsActive) && (g_gl.currentNormalMapTexture != 0) && (boundTexture == g_gl.currentNormalMapTexture);
const bool taggedGlowUnit = (!arbProgramsActive) && tex && tex->isGlowMap;
const bool explicitGlowUnit = (!arbProgramsActive) && (g_gl.currentGlowMapTexture != 0) && (boundTexture == g_gl.currentGlowMapTexture);
const bool taggedSpecularUnit = (!arbProgramsActive) && tex && tex->isSpecularMap;
const bool explicitSpecularUnit = (!arbProgramsActive) && (g_gl.currentSpecularMapTexture != 0) && (boundTexture == g_gl.currentSpecularMapTexture);
const bool arbUnit = arbProgramsActive && (boundTexture != 0);
if ((arbUnit || fixedColorUnit || taggedNormalUnit || explicitNormalUnit || taggedGlowUnit || explicitGlowUnit || taggedSpecularUnit || explicitSpecularUnit) && tex)
boundTextures[unit] = tex;
}
QD3D12_MixTextureStamp(h, g_gl.currentNormalMapTexture != 0 ? QD3D12_FindTextureResource(g_gl.currentNormalMapTexture) : nullptr);
QD3D12_MixTextureStamp(h, g_gl.currentGlowMapTexture != 0 ? QD3D12_FindTextureResource(g_gl.currentGlowMapTexture) : nullptr);
QD3D12_MixTextureStamp(h, g_gl.currentSpecularMapTexture != 0 ? QD3D12_FindTextureResource(g_gl.currentSpecularMapTexture) : nullptr);
QD3D12_MixTextureStamp(h, g_gl.currentNeuralPOMTexture != 0 ? QD3D12_FindTextureResource(g_gl.currentNeuralPOMTexture) : nullptr);
return h ? h : 1ull;
}
static QD3D12PreparedImmediateTextures QD3D12_PrepareImmediateTextures(bool arbProgramsActive)
{
QD3D12PreparedImmediateTextures prepared{};
prepared.stamp = QD3D12_GatherImmediateTextureCandidates(arbProgramsActive, prepared.boundTextures);
if (g_gl.cachedImmediateTexturesValid &&
g_gl.cachedImmediateTextureStamp == prepared.stamp)
{
return g_gl.cachedImmediateTextures;
}
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
{
TextureResource* tex = prepared.boundTextures[unit];
if (tex && tex != &g_gl.whiteTexture && !tex->gpuValid)
{
EnsureTextureResource(*tex);
UploadTexture(*tex);
}
}
prepared.normalMapTex = QD3D12_SelectNormalMapTexture(prepared.boundTextures);
if (prepared.normalMapTex && prepared.normalMapTex != &g_gl.whiteTexture && !prepared.normalMapTex->gpuValid)
{
EnsureTextureResource(*prepared.normalMapTex);
UploadTexture(*prepared.normalMapTex);
}
if (prepared.normalMapTex && prepared.normalMapTex != &g_gl.whiteTexture && QD3D12_TextureHasNeuralPOMData(*prepared.normalMapTex))
QD3D12_UploadNeuralPOM(*prepared.normalMapTex);
if (g_gl.currentNeuralPOMTexture != 0)
{
TextureResource* explicitNeural = QD3D12_FindTextureResource(g_gl.currentNeuralPOMTexture);
if (explicitNeural && QD3D12_TextureHasNeuralPOMData(*explicitNeural))
QD3D12_UploadNeuralPOM(*explicitNeural);
}
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
{
TextureResource* neuralCandidate = prepared.boundTextures[unit];
if (neuralCandidate && neuralCandidate != &g_gl.whiteTexture && QD3D12_TextureHasNeuralPOMData(*neuralCandidate))
QD3D12_UploadNeuralPOM(*neuralCandidate);
}
prepared.glowMapTex = QD3D12_SelectGlowMapTexture(prepared.boundTextures);
if (prepared.glowMapTex && prepared.glowMapTex != &g_gl.whiteTexture && !prepared.glowMapTex->gpuValid)
{
EnsureTextureResource(*prepared.glowMapTex);
UploadTexture(*prepared.glowMapTex);
}
prepared.specularMapTex = QD3D12_SelectSpecularMapTexture(prepared.boundTextures);
if (prepared.specularMapTex && prepared.specularMapTex != &g_gl.whiteTexture && !prepared.specularMapTex->gpuValid)
{
EnsureTextureResource(*prepared.specularMapTex);
UploadTexture(*prepared.specularMapTex);
}
prepared.stamp = QD3D12_GatherImmediateTextureCandidates(arbProgramsActive, prepared.boundTextures);
g_gl.cachedImmediateTextures = prepared;
g_gl.cachedImmediateTextureStamp = prepared.stamp;
g_gl.cachedImmediateTexturesValid = true;
return prepared;
}
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>((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>((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<Mat4>& 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<UINT>(1u, ((UINT)width + 3u) / 4u);
}
static UINT QD3D12_CompressedTextureBlocksHigh(GLsizei height)
{
return std::max<UINT>(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<float>(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<ID3D12Resource>& resource,
D3D12_RESOURCE_STATES& state,
UINT& srvIndex,
D3D12_CPU_DESCRIPTOR_HANDLE& srvCpu,
D3D12_GPU_DESCRIPTOR_HANDLE& srvGpu,
const std::vector<uint8_t>& 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;
}
}
static bool QD3D12_IsEmbeddedEditorWindow(const QD3D12Window& w)
{
return w.hwnd && GetParent(w.hwnd) != nullptr;
}
static bool QD3D12_GetWindowClassName(const QD3D12Window& w, char* className, int classNameBytes)
{
if (!w.hwnd || !className || classNameBytes <= 0)
return false;
className[0] = '\0';
return GetClassNameA(w.hwnd, className, classNameBytes) > 0;
}
static bool QD3D12_IsRadiantCameraWindow(const QD3D12Window& w)
{
char className[64] = {};
if (!QD3D12_GetWindowClassName(w, className, sizeof(className)))
return false;
return strcmp(className, "QCamera") == 0 ||
strcmp(className, "Q3DFXCamera") == 0;
}
static bool QD3D12_IsGameWindow(const QD3D12Window& w)
{
char className[64] = {};
if (!QD3D12_GetWindowClassName(w, className, sizeof(className)))
return false;
return strcmp(className, "DOOM3") == 0;
}
static bool QD3D12_IsFullRenderEmbeddedWindow(const QD3D12Window& w)
{
return QD3D12_IsRadiantCameraWindow(w) ||
QD3D12_IsGameWindow(w);
}
static bool QD3D12_AllowsGamePresentationFeatures(const QD3D12Window& w)
{
return !QD3D12_IsEmbeddedEditorWindow(w) ||
QD3D12_IsGameWindow(w);
}
static bool QD3D12_IsNativeEditorPreviewWindow(const QD3D12Window& w)
{
return QD3D12_IsEmbeddedEditorWindow(w) &&
!QD3D12_IsFullRenderEmbeddedWindow(w);
}
#if defined(QD3D12_ENABLE_STREAMLINE)
struct QD3D12StreamlineState
{
bool initialized = false;
bool deviceBound = false;
bool dlssSupported = false;
bool dlssRrSupported = false;
bool dlssGSupported = false;
bool dlssGFeatureLoaded = false;
bool reflexSupported = false;
bool dlssGActive = false;
uint32_t lastDlssGStatusLogged = 0;
uint32_t lastDlssGEligibilityMaskLogged = 0xffffffffu;
uint32_t lastDlssGFramesToGenerateLogged = 0xffffffffu;
uint32_t lastDlssGPresentedFramesLogged = 0xffffffffu;
uint32_t dlssGActiveFrameMultiplier = 1;
uint32_t dlssGConfiguredFramesToGenerate = 0xffffffffu;
uint32_t dlssGConfiguredRenderWidth = 0xffffffffu;
uint32_t dlssGConfiguredRenderHeight = 0xffffffffu;
uint32_t dlssGConfiguredColorWidth = 0xffffffffu;
uint32_t dlssGConfiguredColorHeight = 0xffffffffu;
sl::ViewportHandle viewport = { 0 };
};
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 bool QD3D12_GetExecutableDirectoryA(char* outPath, size_t outPathSize)
{
if (!outPath || outPathSize == 0)
return false;
DWORD len = GetModuleFileNameA(nullptr, outPath, (DWORD)outPathSize);
if (len == 0 || len >= outPathSize)
return false;
for (char* p = outPath + len; p > outPath; --p)
{
if (p[-1] == '\\' || p[-1] == '/')
{
p[-1] = '\0';
return true;
}
}
return false;
}
static bool QD3D12_GetExecutableDirectoryW(wchar_t* outPath, size_t outPathSize)
{
if (!outPath || outPathSize == 0)
return false;
DWORD len = GetModuleFileNameW(nullptr, outPath, (DWORD)outPathSize);
if (len == 0 || len >= outPathSize)
return false;
for (wchar_t* p = outPath + len; p > outPath; --p)
{
if (p[-1] == L'\\' || p[-1] == L'/')
{
p[-1] = L'\0';
return true;
}
}
return false;
}
static bool QD3D12_ExecutableDirectoryHasFile(const char* dir, const char* name)
{
if (!dir || !name)
return false;
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s\\%s", dir, name);
return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
}
static bool QD3D12_CheckStreamlineFrameGenerationRuntimeFiles()
{
char exeDir[MAX_PATH] = {};
if (!QD3D12_GetExecutableDirectoryA(exeDir, sizeof(exeDir)))
{
QD3D12_Log("DLSS_G runtime check failed: could not resolve executable directory.");
return false;
}
struct RequiredFile
{
const char* name;
bool requiredForFrameGeneration;
};
const RequiredFile files[] =
{
{ "sl.interposer.dll", true },
{ "sl.common.dll", true },
{ "sl.dlss.dll", true },
{ "sl.dlss_d.dll", true },
{ "sl.dlss_g.dll", true },
{ "sl.reflex.dll", true },
{ "sl.pcl.dll", false },
{ "nvngx_dlss.dll", true },
{ "nvngx_dlssd.dll", true },
{ "nvngx_dlssg.dll", true }
};
bool ok = true;
for (const RequiredFile& file : files)
{
const bool present = QD3D12_ExecutableDirectoryHasFile(exeDir, file.name);
if (!present && file.requiredForFrameGeneration)
{
QD3D12_Log("DLSS_G runtime missing %s beside the executable in %s.", file.name, exeDir);
ok = false;
}
}
if (!ok)
{
QD3D12_Log("DLSS_G cannot generate frames until the Streamline DLSS-G runtime DLLs are copied beside the game executable.");
}
return ok;
}
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<QD3D12_PFN_CreateDXGIFactory1>(GetProcAddress(module, "CreateDXGIFactory1"));
g_qd3d12SlInterposer.d3d12CreateDevice =
reinterpret_cast<QD3D12_PFN_D3D12CreateDevice>(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::eMaxQuality;
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 = false;
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;
pref.flags |= sl::PreferenceFlags::eUseDXGIFactoryProxy;
pref.flags &= ~sl::PreferenceFlags::eAllowOTA;
pref.flags &= ~sl::PreferenceFlags::eLoadDownloadedPlugins;
static wchar_t pluginPath[MAX_PATH] = {};
static const wchar_t* pluginPaths[] = { pluginPath };
if (QD3D12_GetExecutableDirectoryW(pluginPath, _countof(pluginPath)))
{
pref.pathsToPlugins = pluginPaths;
pref.numPathsToPlugins = _countof(pluginPaths);
pref.pathToLogsAndData = pluginPath;
}
QD3D12_CheckStreamlineFrameGenerationRuntimeFiles();
sl::Feature features[] =
{
sl::kFeatureDLSS,
sl::kFeatureDLSS_RR,
sl::kFeatureDLSS_G,
sl::kFeatureReflex
};
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;
g_qd3d12Sl.dlssGFeatureLoaded = true;
}
static void QD3D12_CheckStreamlineFeatureSupport()
{
g_qd3d12Sl.dlssSupported = false;
g_qd3d12Sl.dlssRrSupported = false;
g_qd3d12Sl.dlssGSupported = false;
g_qd3d12Sl.dlssGFeatureLoaded = false;
g_qd3d12Sl.reflexSupported = false;
if (!g_qd3d12Sl.initialized || !g_gl.device)
return;
LUID luid = g_gl.device->GetAdapterLuid();
sl::AdapterInfo adapterInfo{};
adapterInfo.deviceLUID = reinterpret_cast<uint8_t*>(&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;
}
const sl::Result fgSupport = slIsFeatureSupported(sl::kFeatureDLSS_G, adapterInfo);
g_qd3d12Sl.dlssGSupported = (fgSupport == sl::Result::eOk);
if (!g_qd3d12Sl.dlssGSupported)
{
QD3D12_Log("slIsFeatureSupported(DLSS_G) failed (%d); frame generation will stay off.", int(fgSupport));
}
bool dlssGLoaded = false;
const sl::Result fgLoadedResult = slIsFeatureLoaded(sl::kFeatureDLSS_G, dlssGLoaded);
g_qd3d12Sl.dlssGFeatureLoaded = (fgLoadedResult == sl::Result::eOk && dlssGLoaded);
if (!g_qd3d12Sl.dlssGFeatureLoaded)
{
QD3D12_Log("slIsFeatureLoaded(DLSS_G) failed or returned unloaded (result=%d loaded=%d).", int(fgLoadedResult), dlssGLoaded ? 1 : 0);
}
const sl::Result reflexSupport = slIsFeatureSupported(sl::kFeatureReflex, adapterInfo);
g_qd3d12Sl.reflexSupported = (reflexSupport == sl::Result::eOk);
if (!g_qd3d12Sl.reflexSupported)
{
QD3D12_Log("slIsFeatureSupported(Reflex) failed (%d); frame generation will stay off.", int(reflexSupport));
}
}
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 false;
}
static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window& w)
{
return !w.isPbuffer &&
QD3D12_AllowsGamePresentationFeatures(w) &&
g_gl.cameraState.valid &&
QD3D12_WantsDLSSRayReconstruction();
}
static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window& w)
{
return QD3D12_CanUseDLSSRayReconstructionForLighting(w) &&
!g_gl.blockLightingUpscaleInputThisFrame &&
g_gl.raytracedLightingReadyThisFrame &&
g_lightingTexture[g_lightingReadIndex] &&
g_lightingTexture[g_lightingReadIndex]->texture;
}
static sl::Result QD3D12_SetStreamlineCommonConstants(sl::FrameToken& frameToken)
{
if (g_gl.streamlineConstantsSerial == g_gl.frameSerial)
return sl::Result::eOk;
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 };
const float motionScaleX = g_currentWindow ? (float)g_currentWindow->renderWidth : 1.0f;
const float motionScaleY = g_currentWindow ? (float)g_currentWindow->renderHeight : 1.0f;
consts.mvecScale = { motionScaleX, motionScaleY }; // motion vectors are written in PreviousUV - CurrentUV
consts.cameraPinholeOffset = { 0.0f, 0.0f };
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;
const sl::Result result = slSetConstants(consts, frameToken, g_qd3d12Sl.viewport);
if (result == sl::Result::eOk)
g_gl.streamlineConstantsSerial = g_gl.frameSerial;
return result;
}
static sl::Result QD3D12_GetStreamlineFrameToken(sl::FrameToken*& frameToken)
{
if (g_gl.streamlineFrameToken &&
g_gl.streamlineFrameTokenSerial == g_gl.frameSerial)
{
frameToken = g_gl.streamlineFrameToken;
return sl::Result::eOk;
}
frameToken = nullptr;
uint32_t frameIndex = (uint32_t)g_gl.frameSerial;
const sl::Result result = slGetNewFrameToken(frameToken, &frameIndex);
if (result == sl::Result::eOk && frameToken)
{
g_gl.streamlineFrameToken = frameToken;
g_gl.streamlineFrameTokenSerial = g_gl.frameSerial;
g_gl.streamlineConstantsSerial = UINT64_MAX;
}
return result;
}
static sl::Result QD3D12_EnableReflexForFrame(sl::FrameToken& frameToken)
{
sl::ReflexOptions reflexOptions{};
reflexOptions.mode = sl::ReflexMode::eLowLatencyWithBoost;
sl::Result result = slReflexSetOptions(reflexOptions);
if (result != sl::Result::eOk)
return result;
result = slReflexSleep(frameToken);
if (result != sl::Result::eOk)
QD3D12_Log("slReflexSleep failed (%d), continuing with frame generation setup.", int(result));
return sl::Result::eOk;
}
static bool QD3D12_WantsDLSSFrameGeneration(const QD3D12Window& w)
{
return
!w.isPbuffer &&
QD3D12_AllowsGamePresentationFeatures(w) &&
g_gl.cameraState.valid &&
g_qd3d12Sl.deviceBound &&
g_qd3d12Sl.dlssGSupported &&
g_qd3d12Sl.dlssGFeatureLoaded &&
g_qd3d12Sl.reflexSupported &&
g_gl.frameGenerationMultiplier >= 2 &&
w.backBuffers[w.frameIndex] &&
w.depthBuffer &&
w.velocityBuffers[w.frameIndex];
}
static uint32_t QD3D12_GetDLSSFrameGenerationEligibilityMask(const QD3D12Window& w)
{
uint32_t mask = 0;
if (w.isPbuffer)
mask |= 1u << 0;
if (!QD3D12_AllowsGamePresentationFeatures(w))
mask |= 1u << 8;
if (!g_gl.cameraState.valid)
mask |= 1u << 1;
if (!g_qd3d12Sl.deviceBound)
mask |= 1u << 2;
if (!g_qd3d12Sl.dlssGSupported)
mask |= 1u << 3;
if (!g_qd3d12Sl.dlssGFeatureLoaded)
mask |= 1u << 4;
if (!g_qd3d12Sl.reflexSupported)
mask |= 1u << 5;
if (g_gl.frameGenerationMultiplier < 2)
mask |= 1u << 6;
if (!w.backBuffers[w.frameIndex] || !w.depthBuffer || !w.velocityBuffers[w.frameIndex])
mask |= 1u << 7;
return mask;
}
static void QD3D12_LogDLSSFrameGenerationEligibility(const QD3D12Window& w, uint32_t mask)
{
if (g_qd3d12Sl.lastDlssGEligibilityMaskLogged == mask)
return;
g_qd3d12Sl.lastDlssGEligibilityMaskLogged = mask;
if (mask == 0)
{
QD3D12_Log("DLSS_G eligible: requested %ux frame generation.", g_gl.frameGenerationMultiplier);
return;
}
QD3D12_Log(
"DLSS_G inactive: r_frameGen=%u, pbuffer=%d, embedded=%d, camera=%d, deviceBound=%d, dlssG=%d, reflex=%d, resources=%d.",
g_gl.frameGenerationMultiplier,
w.isPbuffer ? 1 : 0,
QD3D12_IsEmbeddedEditorWindow(w) ? 1 : 0,
g_gl.cameraState.valid ? 1 : 0,
g_qd3d12Sl.deviceBound ? 1 : 0,
(g_qd3d12Sl.dlssGSupported && g_qd3d12Sl.dlssGFeatureLoaded) ? 1 : 0,
g_qd3d12Sl.reflexSupported ? 1 : 0,
(w.backBuffers[w.frameIndex] && w.depthBuffer && w.velocityBuffers[w.frameIndex]) ? 1 : 0);
}
static void QD3D12_SetDLSSFrameGenerationOff()
{
if (g_qd3d12Sl.deviceBound && g_qd3d12Sl.dlssGSupported && g_qd3d12Sl.dlssGActive)
{
sl::DLSSGOptions options{};
options.mode = sl::DLSSGMode::eOff;
options.flags = sl::DLSSGFlags::eRetainResourcesWhenOff;
const sl::Result result = slDLSSGSetOptions(g_qd3d12Sl.viewport, options);
if (result != sl::Result::eOk)
QD3D12_Log("slDLSSGSetOptions(off) failed (%d).", int(result));
}
if (g_qd3d12Sl.reflexSupported)
{
sl::ReflexOptions reflexOptions{};
reflexOptions.mode = sl::ReflexMode::eOff;
slReflexSetOptions(reflexOptions);
}
g_qd3d12Sl.dlssGActive = false;
g_qd3d12Sl.dlssGActiveFrameMultiplier = 1;
g_qd3d12Sl.dlssGConfiguredFramesToGenerate = 0xffffffffu;
g_qd3d12Sl.dlssGConfiguredRenderWidth = 0xffffffffu;
g_qd3d12Sl.dlssGConfiguredRenderHeight = 0xffffffffu;
g_qd3d12Sl.dlssGConfiguredColorWidth = 0xffffffffu;
g_qd3d12Sl.dlssGConfiguredColorHeight = 0xffffffffu;
}
static void QD3D12_SetDLSSGFeatureLoaded(bool loaded)
{
if (!g_qd3d12Sl.initialized || g_qd3d12Sl.dlssGFeatureLoaded == loaded)
return;
const sl::Result result = slSetFeatureLoaded(sl::kFeatureDLSS_G, loaded);
if (result != sl::Result::eOk)
{
QD3D12_Log("slSetFeatureLoaded(DLSS_G, %d) failed (%d).", loaded ? 1 : 0, int(result));
return;
}
g_qd3d12Sl.dlssGFeatureLoaded = loaded;
QD3D12_Log("DLSS_G feature %s for swap-chain recreation.", loaded ? "loaded" : "unloaded");
}
static void QD3D12_TagDLSSFrameGenerationInputs(
QD3D12Window& w,
sl::FrameToken& frameToken,
const sl::Extent& renderExtent,
const sl::Extent& outputExtent)
{
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
if (!cl)
return;
sl::Resource hudless = { sl::ResourceType::eTex2d, w.backBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.backBufferState[w.frameIndex]) };
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{ &hudless, sl::kBufferTypeHUDLessColor, sl::ResourceLifecycle::eValidUntilPresent, &outputExtent },
sl::ResourceTag{ &depth, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent, &renderExtent },
sl::ResourceTag{ &mvec, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eValidUntilPresent, &renderExtent }
};
slSetTagForFrame(frameToken, g_qd3d12Sl.viewport, tags, _countof(tags), cl);
}
static void QD3D12_ConfigureDLSSFrameGeneration(QD3D12Window& w, sl::FrameToken* existingFrameToken)
{
const uint32_t eligibilityMask = QD3D12_GetDLSSFrameGenerationEligibilityMask(w);
if (g_gl.frameGenerationMultiplier >= 2 && !w.isPbuffer)
QD3D12_LogDLSSFrameGenerationEligibility(w, eligibilityMask);
if (eligibilityMask != 0 || !QD3D12_WantsDLSSFrameGeneration(w))
{
QD3D12_SetDLSSFrameGenerationOff();
return;
}
sl::FrameToken* frameToken = existingFrameToken;
if (!frameToken)
{
const sl::Result frameResult = QD3D12_GetStreamlineFrameToken(frameToken);
if (frameResult != sl::Result::eOk || !frameToken)
{
QD3D12_Log("slGetNewFrameToken for DLSS_G failed (%d), disabling frame generation for this frame.", int(frameResult));
QD3D12_SetDLSSFrameGenerationOff();
return;
}
}
const sl::Result reflexResult = QD3D12_EnableReflexForFrame(*frameToken);
if (reflexResult != sl::Result::eOk)
{
QD3D12_Log("slReflexSetOptions failed (%d), disabling frame generation for this frame.", int(reflexResult));
QD3D12_SetDLSSFrameGenerationOff();
return;
}
const sl::Result constResult = QD3D12_SetStreamlineCommonConstants(*frameToken);
if (constResult != sl::Result::eOk)
{
QD3D12_Log("slSetConstants for DLSS_G failed (%d), disabling frame generation for this frame.", int(constResult));
QD3D12_SetDLSSFrameGenerationOff();
return;
}
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;
sl::DLSSGOptions options{};
options.mode = sl::DLSSGMode::eOn;
options.numFramesToGenerate = ClampValue<uint32_t>(g_gl.frameGenerationMultiplier - 1, 1, 3);
options.flags = sl::DLSSGFlags::eRetainResourcesWhenOff;
options.numBackBuffers = QD3D12_FrameCount;
options.mvecDepthWidth = w.renderWidth;
options.mvecDepthHeight = w.renderHeight;
options.colorWidth = w.width;
options.colorHeight = w.height;
options.colorBufferFormat = uint32_t(DXGI_FORMAT_R8G8B8A8_UNORM);
options.mvecBufferFormat = uint32_t(QD3D12_VelocityFormat);
options.depthBufferFormat = uint32_t(QD3D12_DepthSrvFormat);
options.hudLessBufferFormat = uint32_t(DXGI_FORMAT_R8G8B8A8_UNORM);
options.uiBufferFormat = uint32_t(DXGI_FORMAT_R8G8B8A8_UNORM);
const bool configMayNeedUpdate =
!g_qd3d12Sl.dlssGActive ||
g_qd3d12Sl.dlssGConfiguredFramesToGenerate != options.numFramesToGenerate ||
g_qd3d12Sl.dlssGConfiguredRenderWidth != w.renderWidth ||
g_qd3d12Sl.dlssGConfiguredRenderHeight != w.renderHeight ||
g_qd3d12Sl.dlssGConfiguredColorWidth != w.width ||
g_qd3d12Sl.dlssGConfiguredColorHeight != w.height;
sl::DLSSGState state{};
const sl::Result stateResult = slDLSSGGetState(g_qd3d12Sl.viewport, state, configMayNeedUpdate ? &options : nullptr);
if (stateResult == sl::Result::eOk)
{
if (state.numFramesToGenerateMax > 0)
options.numFramesToGenerate = min(options.numFramesToGenerate, state.numFramesToGenerateMax);
if (state.status != sl::DLSSGStatus::eOk)
{
const uint32_t statusBits = uint32_t(state.status);
if (g_qd3d12Sl.lastDlssGStatusLogged != statusBits)
{
QD3D12_Log("DLSS_G reported status 0x%08x; leaving frame generation off for this frame.", statusBits);
g_qd3d12Sl.lastDlssGStatusLogged = statusBits;
}
QD3D12_SetDLSSFrameGenerationOff();
return;
}
g_qd3d12Sl.lastDlssGStatusLogged = 0;
}
else
{
QD3D12_Log("slDLSSGGetState failed (%d), trying DLSS_G set-options path anyway.", int(stateResult));
}
QD3D12_TagDLSSFrameGenerationInputs(w, *frameToken, renderExtent, outputExtent);
const bool needsSetOptions =
!g_qd3d12Sl.dlssGActive ||
g_qd3d12Sl.dlssGConfiguredFramesToGenerate != options.numFramesToGenerate ||
g_qd3d12Sl.dlssGConfiguredRenderWidth != w.renderWidth ||
g_qd3d12Sl.dlssGConfiguredRenderHeight != w.renderHeight ||
g_qd3d12Sl.dlssGConfiguredColorWidth != w.width ||
g_qd3d12Sl.dlssGConfiguredColorHeight != w.height;
if (needsSetOptions)
{
const sl::Result setResult = slDLSSGSetOptions(g_qd3d12Sl.viewport, options);
if (setResult != sl::Result::eOk)
{
QD3D12_Log("slDLSSGSetOptions(on) failed (%d).", int(setResult));
QD3D12_SetDLSSFrameGenerationOff();
return;
}
g_qd3d12Sl.dlssGConfiguredFramesToGenerate = options.numFramesToGenerate;
g_qd3d12Sl.dlssGConfiguredRenderWidth = w.renderWidth;
g_qd3d12Sl.dlssGConfiguredRenderHeight = w.renderHeight;
g_qd3d12Sl.dlssGConfiguredColorWidth = w.width;
g_qd3d12Sl.dlssGConfiguredColorHeight = w.height;
}
if (!g_qd3d12Sl.dlssGActive || g_qd3d12Sl.lastDlssGFramesToGenerateLogged != options.numFramesToGenerate)
{
QD3D12_Log(
"DLSS_G active: requested %ux, generating %u frame(s) between rendered frames.",
g_gl.frameGenerationMultiplier,
options.numFramesToGenerate);
g_qd3d12Sl.lastDlssGFramesToGenerateLogged = options.numFramesToGenerate;
}
g_qd3d12Sl.dlssGActive = true;
g_qd3d12Sl.dlssGActiveFrameMultiplier = options.numFramesToGenerate + 1;
}
static void QD3D12_LogDLSSFrameGenerationPresentResult()
{
if (!g_qd3d12Sl.deviceBound || !g_qd3d12Sl.dlssGSupported || !g_qd3d12Sl.dlssGActive)
return;
sl::DLSSGState state{};
const sl::Result stateResult = slDLSSGGetState(g_qd3d12Sl.viewport, state, nullptr);
if (stateResult != sl::Result::eOk)
return;
if (state.numFramesActuallyPresented != g_qd3d12Sl.lastDlssGPresentedFramesLogged)
{
QD3D12_Log(
"DLSS_G present: Streamline presented %u frame(s) since last check.",
state.numFramesActuallyPresented);
g_qd3d12Sl.lastDlssGPresentedFramesLogged = state.numFramesActuallyPresented;
}
}
#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; }
static void QD3D12_ConfigureDLSSFrameGeneration(QD3D12Window&, void*) {}
static void QD3D12_LogDLSSFrameGenerationPresentResult() {}
#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 (outputWidth == 0 || outputHeight == 0)
{
w.renderWidth = 1;
w.renderHeight = 1;
return;
}
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<UINT>(1, (UINT)std::lround((double)outputWidth * ratio));
w.renderHeight = std::max<UINT>(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,
float screenDecalCount = 0.0f,
float screenDecalIndex = 0.0f,
bool forceCameraPosition = false,
float screenParticleSoftDepth = 16.0f,
float screenParticleLightCount = 0.0f,
float screenParticleEmissiveScale = 0.0f,
float screenParticleOutputMode = 0.0f)
{
if (!cl)
return;
float constants[QD3D12_PostConstantDwords] = {};
constants[0] = sharpness;
constants[1] = taaHistoryWeight;
constants[2] = taaReset;
constants[3] = screenDecalIndex;
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];
}
else if ( forceCameraPosition && g_gl.cameraState.valid )
{
constants[13] = 1.0f;
constants[20] = g_gl.cameraState.cameraPos[0];
constants[21] = g_gl.cameraState.cameraPos[1];
constants[22] = g_gl.cameraState.cameraPos[2];
}
constants[14] = screenParticleSoftDepth;
constants[15] = screenParticleLightCount;
constants[23] = screenDecalCount;
constants[24] = screenParticleEmissiveScale;
constants[25] = screenParticleOutputMode;
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 bool QD3D12_EnsureScreenSpaceDecalBuffer()
{
if (!g_gl.device || !g_gl.srvHeap)
return false;
const UINT64 bufferSize = sizeof(glScreenSpaceDecal_t) * QD3D12_MaxScreenSpaceDecals;
if (!g_gl.screenSpaceDecalBuffer)
{
D3D12_HEAP_PROPERTIES hp{};
hp.Type = D3D12_HEAP_TYPE_UPLOAD;
hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
hp.CreationNodeMask = 1;
hp.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC rd{};
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
rd.Alignment = 0;
rd.Width = bufferSize;
rd.Height = 1;
rd.DepthOrArraySize = 1;
rd.MipLevels = 1;
rd.Format = DXGI_FORMAT_UNKNOWN;
rd.SampleDesc.Count = 1;
rd.SampleDesc.Quality = 0;
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_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&g_gl.screenSpaceDecalBuffer)));
QD3D12_CHECK(g_gl.screenSpaceDecalBuffer->Map(0, nullptr, &g_gl.screenSpaceDecalBufferMapped));
}
if (g_gl.screenSpaceDecalSrvIndex == UINT_MAX)
{
g_gl.screenSpaceDecalSrvIndex = g_gl.nextSrvIndex++;
g_gl.screenSpaceDecalSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceDecalSrvIndex);
g_gl.screenSpaceDecalSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceDecalSrvIndex);
D3D12_SHADER_RESOURCE_VIEW_DESC sd{};
sd.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
sd.Format = DXGI_FORMAT_UNKNOWN;
sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
sd.Buffer.FirstElement = 0;
sd.Buffer.NumElements = QD3D12_MaxScreenSpaceDecals;
sd.Buffer.StructureByteStride = sizeof(glScreenSpaceDecal_t);
sd.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
g_gl.device->CreateShaderResourceView(g_gl.screenSpaceDecalBuffer.Get(), &sd, g_gl.screenSpaceDecalSrvCpu);
}
if (g_gl.screenSpaceDecalBufferMapped)
{
memset(g_gl.screenSpaceDecalBufferMapped, 0, bufferSize);
if (g_gl.screenSpaceDecalCount > 0)
{
memcpy(
g_gl.screenSpaceDecalBufferMapped,
g_gl.screenSpaceDecals,
sizeof(glScreenSpaceDecal_t) * g_gl.screenSpaceDecalCount);
}
}
return true;
}
static bool QD3D12_EnsureScreenSpaceParticleLightBuffer()
{
if (!g_gl.device || !g_gl.srvHeap)
return false;
const UINT64 bufferSize = sizeof(glRaytracingLight_t) * QD3D12_MaxScreenSpaceParticleLights;
if (!g_gl.screenSpaceParticleLightBuffer)
{
D3D12_HEAP_PROPERTIES hp{};
hp.Type = D3D12_HEAP_TYPE_UPLOAD;
hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
hp.CreationNodeMask = 1;
hp.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC rd{};
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
rd.Width = bufferSize;
rd.Height = 1;
rd.DepthOrArraySize = 1;
rd.MipLevels = 1;
rd.Format = DXGI_FORMAT_UNKNOWN;
rd.SampleDesc.Count = 1;
rd.SampleDesc.Quality = 0;
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_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&g_gl.screenSpaceParticleLightBuffer)));
QD3D12_CHECK(g_gl.screenSpaceParticleLightBuffer->Map(0, nullptr, &g_gl.screenSpaceParticleLightBufferMapped));
}
if (g_gl.screenSpaceParticleLightSrvIndex == UINT_MAX)
{
g_gl.screenSpaceParticleLightSrvIndex = g_gl.nextSrvIndex++;
g_gl.screenSpaceParticleLightSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceParticleLightSrvIndex);
g_gl.screenSpaceParticleLightSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceParticleLightSrvIndex);
D3D12_SHADER_RESOURCE_VIEW_DESC sd{};
sd.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
sd.Format = DXGI_FORMAT_UNKNOWN;
sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
sd.Buffer.FirstElement = 0;
sd.Buffer.NumElements = QD3D12_MaxScreenSpaceParticleLights;
sd.Buffer.StructureByteStride = sizeof(glRaytracingLight_t);
sd.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
g_gl.device->CreateShaderResourceView(g_gl.screenSpaceParticleLightBuffer.Get(), &sd, g_gl.screenSpaceParticleLightSrvCpu);
}
if (g_gl.screenSpaceParticleLightBufferMapped)
{
memset(g_gl.screenSpaceParticleLightBufferMapped, 0, bufferSize);
if (g_gl.screenSpaceParticleLightCount > 0)
{
memcpy(
g_gl.screenSpaceParticleLightBufferMapped,
g_gl.screenSpaceParticleLights,
sizeof(glRaytracingLight_t) * g_gl.screenSpaceParticleLightCount);
}
}
return true;
}
static bool QD3D12_UpdateScreenSpaceParticleTlasSrv(ID3D12Resource* tlas)
{
if (!g_gl.device || !g_gl.srvHeap || !tlas)
return false;
if (g_gl.screenSpaceParticleTlasSrvIndex == UINT_MAX)
{
g_gl.screenSpaceParticleTlasSrvIndex = g_gl.nextSrvIndex++;
g_gl.screenSpaceParticleTlasSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceParticleTlasSrvIndex);
g_gl.screenSpaceParticleTlasSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceParticleTlasSrvIndex);
}
D3D12_SHADER_RESOURCE_VIEW_DESC sd{};
sd.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE;
sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
sd.RaytracingAccelerationStructure.Location = tlas->GetGPUVirtualAddress();
g_gl.device->CreateShaderResourceView(nullptr, &sd, g_gl.screenSpaceParticleTlasSrvCpu);
return true;
}
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);
}
void APIENTRY glSetScreenSpaceDecalsQD3D12(const glScreenSpaceDecal_t* decals, uint32_t decalCount)
{
const uint32_t count = std::min<uint32_t>(decalCount, QD3D12_MaxScreenSpaceDecals);
g_gl.screenSpaceDecalCount = count;
memset(g_gl.screenSpaceDecals, 0, sizeof(g_gl.screenSpaceDecals));
if (decals && count > 0)
{
memcpy(g_gl.screenSpaceDecals, decals, sizeof(glScreenSpaceDecal_t) * count);
}
}
void APIENTRY glDrawScreenSpaceDecalsQD3D12(void)
{
if (g_gl.screenSpaceDecalCount == 0)
return;
QD3D12Window* window = g_currentWindow;
if (!window || !g_gl.device || !g_gl.cmdList || !g_gl.postScreenDecalPSO)
return;
if (!window->sceneColorBuffers[window->frameIndex] || !window->normalBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex])
return;
if (!QD3D12_EnsureScreenSpaceDecalBuffer())
return;
QD3D12_EnsureFrameOpen();
QD3D12_FlushQueuedBatches();
QD3D12_ResolveGBufferForCurrentFrame(*window);
const UINT frame = window->frameIndex;
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
QD3D12_TransitionResource(cl, window->normalBuffers[frame].Get(), window->normalBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
QD3D12_TransitionResource(cl, window->positionBuffers[frame].Get(), window->positionBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
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;
ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() };
cl->SetDescriptorHeaps(_countof(heaps), heaps);
cl->SetGraphicsRootSignature(g_gl.postRootSig.Get());
QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, (float)g_gl.screenSpaceDecalCount);
D3D12_CPU_DESCRIPTOR_HANDLE sceneColorRtv = CurrentResolvedSceneColorRTV();
cl->OMSetRenderTargets(1, &sceneColorRtv, FALSE, nullptr);
cl->RSSetViewports(1, &viewport);
cl->RSSetScissorRects(1, &scissor);
cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
for (uint32_t decalIndex = 0; decalIndex < g_gl.screenSpaceDecalCount; ++decalIndex)
{
D3D12_GPU_DESCRIPTOR_HANDLE decalTextureSrv = g_gl.whiteTexture.srvGpu;
const glScreenSpaceDecal_t& decal = g_gl.screenSpaceDecals[decalIndex];
ID3D12PipelineState* decalPSO = (decal.blendMode == 1 && g_gl.postScreenDecalStainPSO.Get() != nullptr) ? g_gl.postScreenDecalStainPSO.Get() : g_gl.postScreenDecalPSO.Get();
cl->SetPipelineState(decalPSO);
TextureResource* decalTexture = decal.textureId ? QD3D12_FindTextureResource(decal.textureId) : nullptr;
if (decalTexture)
{
EnsureTextureResource(*decalTexture);
if (decalTexture->texture && !decalTexture->gpuValid)
{
UploadTexture(*decalTexture);
}
if (decalTexture->texture && decalTexture->srvIndex != UINT_MAX)
{
decalTextureSrv = decalTexture->srvGpu;
}
}
QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, (float)g_gl.screenSpaceDecalCount, (float)decalIndex);
cl->SetGraphicsRootDescriptorTable(0, decalTextureSrv);
cl->SetGraphicsRootDescriptorTable(11, window->normalSrvGpu[frame]);
cl->SetGraphicsRootDescriptorTable(12, window->positionSrvGpu[frame]);
cl->SetGraphicsRootDescriptorTable(13, g_gl.screenSpaceDecalSrvGpu);
cl->DrawInstanced(3, 1, 0, 0);
}
}
static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas, bool emissiveOnly)
{
const glScreenSpaceParticleVertex_t* verts = batch.vertices.empty() ? nullptr : batch.vertices.data();
const uint32_t vertexCount = (uint32_t)batch.vertices.size();
const uint32_t textureId = batch.textureId;
const uint32_t blendMode = batch.blendMode;
const float softDepth = batch.softDepth;
const float emissiveScale = batch.emissiveScale;
if (!verts || vertexCount == 0)
return;
if (emissiveOnly && emissiveScale <= 0.0f)
return;
QD3D12Window* window = g_currentWindow;
if (!window || !g_gl.device || !g_gl.cmdList || !g_gl.postScreenParticlePSO)
return;
if (!window->sceneColorBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex] || !window->depthBuffer)
return;
if (emissiveOnly && !window->emissiveBuffers[window->frameIndex])
return;
if (!QD3D12_EnsureScreenSpaceParticleLightBuffer() || !QD3D12_UpdateScreenSpaceParticleTlasSrv(tlas))
return;
QD3D12_EnsureFrameOpen();
QD3D12_FlushQueuedBatches();
QD3D12_ResolveGBufferForCurrentFrame(*window);
QD3D12_UpdateCameraInfoFromCurrentMatrices();
const UINT frame = window->frameIndex;
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
if (emissiveOnly)
{
QD3D12_TransitionResource(cl, window->emissiveBuffers[frame].Get(), window->emissiveBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
}
else
{
QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
}
QD3D12_TransitionResource(cl, window->depthBuffer.Get(), window->depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
QD3D12_TransitionResource(cl, window->positionBuffers[frame].Get(), window->positionBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
const UINT vbBytes = vertexCount * sizeof(glScreenSpaceParticleVertex_t);
UploadAlloc upload = QD3D12_AllocUpload(vbBytes, 256);
memcpy(upload.cpu, verts, vbBytes);
D3D12_VERTEX_BUFFER_VIEW vbv{};
vbv.BufferLocation = upload.gpu;
vbv.SizeInBytes = vbBytes;
vbv.StrideInBytes = sizeof(glScreenSpaceParticleVertex_t);
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;
D3D12_GPU_DESCRIPTOR_HANDLE particleTextureSrv = g_gl.whiteTexture.srvGpu;
TextureResource* particleTexture = textureId ? QD3D12_FindTextureResource(textureId) : nullptr;
if (particleTexture)
{
EnsureTextureResource(*particleTexture);
if (particleTexture->texture && !particleTexture->gpuValid)
{
UploadTexture(*particleTexture);
}
if (particleTexture->texture && particleTexture->srvIndex != UINT_MAX)
{
particleTextureSrv = particleTexture->srvGpu;
}
}
ID3D12PipelineState* particlePSO = emissiveOnly && g_gl.postScreenParticleEmissivePSO.Get()
? g_gl.postScreenParticleEmissivePSO.Get()
: (blendMode == 1 && g_gl.postScreenParticleAdditivePSO.Get() != nullptr)
? g_gl.postScreenParticleAdditivePSO.Get()
: g_gl.postScreenParticlePSO.Get();
ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() };
cl->SetDescriptorHeaps(_countof(heaps), heaps);
cl->SetGraphicsRootSignature(g_gl.postRootSig.Get());
QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount, emissiveScale, emissiveOnly ? 1.0f : 0.0f);
cl->SetPipelineState(particlePSO);
D3D12_CPU_DESCRIPTOR_HANDLE particleRtv = emissiveOnly ? CurrentResolvedEmissiveRTV() : CurrentResolvedSceneColorRTV();
cl->OMSetRenderTargets(1, &particleRtv, FALSE, nullptr);
cl->RSSetViewports(1, &viewport);
cl->RSSetScissorRects(1, &scissor);
cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cl->IASetVertexBuffers(0, 1, &vbv);
cl->SetGraphicsRootDescriptorTable(0, particleTextureSrv);
cl->SetGraphicsRootDescriptorTable(10, window->depthSrvGpu);
cl->SetGraphicsRootDescriptorTable(12, window->positionSrvGpu[frame]);
cl->SetGraphicsRootDescriptorTable(14, g_gl.screenSpaceParticleLightSrvGpu);
cl->SetGraphicsRootDescriptorTable(15, g_gl.screenSpaceParticleTlasSrvGpu);
cl->DrawInstanced(vertexCount, 1, 0, 0);
}
void APIENTRY glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth, float emissiveScale)
{
if (!verts || vertexCount == 0)
return;
QD3D12ScreenSpaceParticleBatch batch;
batch.vertices.assign(verts, verts + vertexCount);
batch.textureId = textureId;
batch.blendMode = blendMode;
batch.softDepth = softDepth;
batch.emissiveScale = emissiveScale;
g_gl.screenSpaceParticleBatches.push_back(std::move(batch));
}
void APIENTRY glSetScreenSpaceParticleLightsQD3D12(const glRaytracingLight_t* lights, uint32_t lightCount)
{
g_gl.screenSpaceParticleLightCount = 0;
memset(g_gl.screenSpaceParticleLights, 0, sizeof(g_gl.screenSpaceParticleLights));
if (!lights || lightCount == 0)
return;
const uint32_t count = std::min<uint32_t>(lightCount, QD3D12_MaxScreenSpaceParticleLights);
memcpy(g_gl.screenSpaceParticleLights, lights, sizeof(glRaytracingLight_t) * count);
g_gl.screenSpaceParticleLightCount = count;
}
static void QD3D12_DrawQueuedScreenSpaceParticles(ID3D12Resource* tlas)
{
if (g_gl.screenSpaceParticleBatches.empty())
return;
for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches)
{
QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, false);
}
g_gl.screenSpaceParticleBatches.clear();
}
static void QD3D12_DrawQueuedScreenSpaceParticleEmissive(ID3D12Resource* tlas)
{
if (g_gl.screenSpaceParticleBatches.empty())
return;
for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches)
{
QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, true);
}
}
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)
{
(void)w;
return false;
}
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_ExecuteMainCommandListForRaytracingHandoff(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;
const UINT slot = g_gl.raytraceContinuationIndex++ % QD3D12_FrameCount;
const UINT64 continuationFence = g_gl.raytraceContinuationFence[slot];
if (continuationFence != 0 && g_gl.fence->GetCompletedValue() < continuationFence)
{
QD3D12_CHECK(g_gl.fence->SetEventOnCompletion(continuationFence, g_gl.fenceEvent));
WaitForSingleObject(g_gl.fenceEvent, INFINITE);
}
if (!g_gl.raytraceContinuationAlloc[slot])
{
QD3D12_CHECK(g_gl.device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&g_gl.raytraceContinuationAlloc[slot])));
}
QD3D12_CHECK(g_gl.raytraceContinuationAlloc[slot]->Reset());
QD3D12_CHECK(g_gl.cmdList->Reset(g_gl.raytraceContinuationAlloc[slot].Get(), nullptr));
g_gl.activeRaytraceContinuationSlot = (int)slot;
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 allowGamePresentationFeatures = QD3D12_AllowsGamePresentationFeatures(w);
const bool haveTemporalCameraInputs = (!w.isPbuffer) && allowGamePresentationFeatures && g_gl.cameraState.valid;
const bool useLightingUpscaleInput = (!w.isPbuffer) && allowGamePresentationFeatures && QD3D12_UseLightingTextureAsUpscaleInput(w);
ID3D12Resource* upscaleInputResource = w.sceneColorBuffers[w.frameIndex].Get();
D3D12_GPU_DESCRIPTOR_HANDLE upscaleInputSrv = w.sceneColorSrvGpu[w.frameIndex];
TextureResource* readLightingTex = g_lightingTexture[g_lightingReadIndex];
if (useLightingUpscaleInput && readLightingTex && readLightingTex->texture)
{
upscaleInputResource = readLightingTex->texture.Get();
upscaleInputSrv = readLightingTex->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;
const sl::Result frameResult = QD3D12_GetStreamlineFrameToken(frameToken);
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;
memcpy(&rrOptions.worldToCameraView, g_gl.cameraState.worldToView.m, sizeof(float) * 16);
memcpy(&rrOptions.cameraViewToWorld, g_gl.cameraState.viewToWorld.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;
const sl::Result frameResult = QD3D12_GetStreamlineFrameToken(frameToken);
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<ID3D12Debug> 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)));
g_gl.adapter.Reset();
g_gl.adapterName.clear();
g_gl.dedicatedVideoMemory = 0;
if (g_gl.factory && g_gl.device)
{
ComPtr<IDXGIAdapter1> adapter1;
if (SUCCEEDED(g_gl.factory->EnumAdapterByLuid(g_gl.device->GetAdapterLuid(), IID_PPV_ARGS(&adapter1))) && adapter1)
{
DXGI_ADAPTER_DESC1 desc{};
if (SUCCEEDED(adapter1->GetDesc1(&desc)))
{
g_gl.dedicatedVideoMemory = desc.DedicatedVideoMemory;
char name[256] = {};
const int chars = WideCharToMultiByte(CP_UTF8, 0, desc.Description, -1, name, sizeof(name), nullptr, nullptr);
if (chars > 0)
g_gl.adapterName = name;
}
adapter1.As(&g_gl.adapter);
}
}
QD3D12_StreamlineOnDeviceCreated();
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)));
}
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;
IDXGISwapChain1* rawSwapChain = nullptr;
QD3D12_CHECK(g_gl.factory->CreateSwapChainForHwnd(
g_gl.queue.Get(),
w.hwnd,
&sd,
nullptr,
nullptr,
&rawSwapChain));
ComPtr<IDXGISwapChain1> sc1;
sc1.Attach(rawSwapChain);
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<ID3D12Resource>& 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<UINT>(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<ID3D12Resource>& 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<UINT>(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<void**>(&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<ID3DBlob> CompileShaderSourceVariant(const char* source, const char* entry, const char* target, LPCWSTR defineName = nullptr)
{
ComPtr<IDxcUtils> utils;
ComPtr<IDxcCompiler3> compiler;
ComPtr<IDxcIncludeHandler> 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<LPCWSTR> 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 (defineName && defineName[0])
{
args.push_back(L"-D");
args.push_back(defineName);
}
#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<IDxcResult> 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<IDxcBlobUtf8> errors;
result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr);
if (errors && errors->GetStringLength() > 0)
{
OutputDebugStringA(errors->GetStringPointer());
OutputDebugStringA("\n");
}
HRESULT status = S_OK;
result->GetStatus(&status);
if (FAILED(status))
{
QD3D12_Fatal("Shader compile failed for %s: %s",
entry,
(errors && errors->GetStringLength() > 0) ? errors->GetStringPointer() : "unknown");
return nullptr;
}
ComPtr<IDxcBlob> 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<ID3DBlob> blob;
blob.Attach(reinterpret_cast<ID3DBlob*>(dxil.Detach()));
return blob;
}
static ComPtr<ID3DBlob> 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<ID3DBlob> sig;
ComPtr<ID3DBlob> 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<ID3DBlob> sig;
ComPtr<ID3DBlob> 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");
g_gl.postPsScreenDecalBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSScreenDecals", "ps_6_0");
g_gl.postVsScreenParticleBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "VSParticle", "vs_6_0");
g_gl.postPsScreenParticleBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSScreenParticles", "ps_6_5", L"QD3D12_SCREEN_PARTICLE_RAYQUERY=1");
}
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 const D3D12_INPUT_ELEMENT_DESC kScreenSpaceParticleInputLayout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, clip), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, st), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, color), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, world), 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);
const bool glassGBuffer = alphaBlended && QD3D12_GeometryFlagHasGlass(key.geometryFlag);
d.BlendState.RenderTarget[0].RenderTargetWriteMask = key.colorWriteMask;
if (!nativeColorOnly)
{
const UINT8 gbufferWriteMask = (key.colorWriteMask && (!alphaBlended || glassGBuffer)) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0;
d.BlendState.RenderTarget[1].RenderTargetWriteMask = gbufferWriteMask;
d.BlendState.RenderTarget[2].RenderTargetWriteMask = gbufferWriteMask;
d.BlendState.RenderTarget[3].RenderTargetWriteMask = gbufferWriteMask;
d.BlendState.RenderTarget[4].RenderTargetWriteMask = gbufferWriteMask;
d.BlendState.RenderTarget[5].RenderTargetWriteMask = gbufferWriteMask;
}
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)));
d.PS = { g_gl.postPsScreenDecalBlob->GetBufferPointer(), g_gl.postPsScreenDecalBlob->GetBufferSize() };
d.BlendState.RenderTarget[0].BlendEnable = TRUE;
d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
d.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
d.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postScreenDecalPSO)));
d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ZERO;
d.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_COLOR;
d.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ZERO;
d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postScreenDecalStainPSO)));
D3D12_GRAPHICS_PIPELINE_STATE_DESC particleDesc = d;
particleDesc.VS = { g_gl.postVsScreenParticleBlob->GetBufferPointer(), g_gl.postVsScreenParticleBlob->GetBufferSize() };
particleDesc.PS = { g_gl.postPsScreenParticleBlob->GetBufferPointer(), g_gl.postPsScreenParticleBlob->GetBufferSize() };
particleDesc.InputLayout.pInputElementDescs = kScreenSpaceParticleInputLayout;
particleDesc.InputLayout.NumElements = _countof(kScreenSpaceParticleInputLayout);
particleDesc.BlendState.RenderTarget[0].BlendEnable = TRUE;
particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticlePSO)));
particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticleAdditivePSO)));
particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticleEmissivePSO)));
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<uint64_t, ComPtr<ID3D12PipelineState>> 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<ID3D12PipelineState> 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);
const bool glassGBuffer = alphaBlended && QD3D12_GeometryFlagHasGlass(key.geometryFlag);
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 : ((!nativeColorOnly && glassGBuffer) ? D3D12_COLOR_WRITE_ENABLE_ALL : 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<uint64_t, ComPtr<ID3D12PipelineState>> 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<ID3D12PipelineState> 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<GLclampd>(g_gl.depthRangeNear, 0.0, 1.0);
w.viewport.MaxDepth = (float)ClampValue<GLclampd>(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();
const bool nativeEditorPreviewWindow = QD3D12_IsNativeEditorPreviewWindow(w);
g_gl.framePhase = nativeEditorPreviewWindow ? QD3D12_FRAME_NATIVE_POST_UPSCALE : QD3D12_FRAME_LOW_RES;
g_gl.sceneResolvedThisFrame = false;
g_gl.gbufferResolvedThisFrame = false;
g_gl.raytracedLightingReadyThisFrame = false;
g_gl.sceneFogValidThisFrame = false;
g_gl.blockLightingUpscaleInputThisFrame = false;
g_gl.screenSpaceParticleBatches.clear();
g_gl.screenSpaceParticleLightCount = 0;
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);
if (!nativeEditorPreviewWindow)
{
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();
const bool nativeEditorPreviewWindow = QD3D12_IsNativeEditorPreviewWindow(w);
const bool allowGamePresentationFeatures = QD3D12_AllowsGamePresentationFeatures(w);
if (!nativeEditorPreviewWindow && !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.
if (allowGamePresentationFeatures)
QD3D12_ProcessCompletedTextureMipJobs(1);
QD3D12_TransitionResource(g_gl.cmdList.Get(), w.backBuffers[w.frameIndex].Get(), w.backBufferState[w.frameIndex], D3D12_RESOURCE_STATE_PRESENT);
#if defined(QD3D12_ENABLE_STREAMLINE)
if (allowGamePresentationFeatures)
QD3D12_ConfigureDLSSFrameGeneration(w, nullptr);
#endif
QD3D12_CHECK(g_gl.cmdList->Close());
ID3D12CommandList* lists[] = { g_gl.cmdList.Get() };
g_gl.queue->ExecuteCommandLists(1, lists);
if (g_gl.activeRaytraceContinuationSlot >= 0)
{
const UINT slot = (UINT)g_gl.activeRaytraceContinuationSlot;
const UINT64 signalValue = g_gl.nextFenceValue++;
QD3D12_CHECK(g_gl.queue->Signal(g_gl.fence.Get(), signalValue));
g_gl.raytraceContinuationFence[slot] = signalValue;
g_gl.activeRaytraceContinuationSlot = -1;
}
g_gl.frameOpen = false;
g_gl.frameOwner = nullptr;
if (!w.isPbuffer && allowGamePresentationFeatures)
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));
#if defined(QD3D12_ENABLE_STREAMLINE)
if (QD3D12_AllowsGamePresentationFeatures(w))
QD3D12_LogDLSSFrameGenerationPresentResult();
#endif
}
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<UINT>(1u, w >> 1);
h = std::max<UINT>(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<UINT>(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, UINT slot)
{
const DXGI_FORMAT desiredFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
slot %= QD3D12_LightingTextureCount;
if (g_lightingTextureId[slot] == 0)
{
g_lightingTextureId[slot] = g_gl.nextTextureId++;
TextureResource tex{};
tex.glId = g_lightingTextureId[slot];
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[slot], std::move(tex)).first;
g_lightingTexture[slot] = &it->second;
}
if (!g_lightingTexture[slot])
{
auto it = g_gl.textures.find(g_lightingTextureId[slot]);
if (it == g_gl.textures.end())
return nullptr;
g_lightingTexture[slot] = &it->second;
}
TextureResource* lightingTex = g_lightingTexture[slot];
lightingTex->dxgiFormat = desiredFormat;
lightingTex->mipLevels = 1;
const bool needsCreate =
!lightingTex->texture ||
lightingTex->width != (int)width ||
lightingTex->height != (int)height ||
lightingTex->texture->GetDesc().Format != desiredFormat ||
((lightingTex->texture->GetDesc().Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) == 0);
if (!needsCreate)
return lightingTex;
if (lightingTex->texture)
{
QD3D12_RetireResource(lightingTex->texture);
}
lightingTex->width = (int)width;
lightingTex->height = (int)height;
lightingTex->compressed = false;
lightingTex->compressedInternalFormat = 0;
lightingTex->compressedBlockBytes = 0;
lightingTex->compressedImageSize = 0;
lightingTex->forceOpaqueAlpha = false;
lightingTex->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(&lightingTex->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(
lightingTex->texture.Get(),
&srv,
lightingTex->srvCpu);
lightingTex->gpuValid = true;
lightingTex->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
g_lightingTextureState[slot] = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
g_lightingTextureValid[slot] = false;
return lightingTex;
}
static void ConvertToRGBA8(const TextureResource& tex, std::vector<uint8_t>& 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<const __m128i*>(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<const __m256i*>(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<const __m256i*>(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<float>(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>((int)floorf(c * 255.0f + 0.5f), 0, 255);
}
static inline const uint8_t* QD3D12_RGBA8PixelClamp(const std::vector<uint8_t>& img, UINT w, UINT h, int x, int y)
{
const UINT cx = ClampValue<UINT>((UINT)max(0, x), 0u, w - 1u);
const UINT cy = ClampValue<UINT>((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<uint8_t>& 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<float>(filtered[0] + (center[0] - filtered[0]) * sharpen, 0.0f, 1.0f);
filtered[1] = ClampValue<float>(filtered[1] + (center[1] - filtered[1]) * sharpen, 0.0f, 1.0f);
filtered[2] = ClampValue<float>(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>((int)floorf(outA * 255.0f + 0.5f), 0, 255);
}
static void QD3D12_BuildRGBA8MipChain(const TextureResource& tex, std::vector<std::vector<uint8_t>>& 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<UINT>(1u, srcW >> 1);
const UINT dstH = std::max<UINT>(1u, srcH >> 1);
const std::vector<uint8_t>& src = outMips[level - 1];
std::vector<uint8_t>& 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<uint8_t> sysmem;
};
struct QD3D12MipBuildResult
{
GLuint textureId = 0;
uint64_t generation = 0;
int width = 0;
int height = 0;
GLenum format = GL_RGBA;
std::vector<std::vector<uint8_t>> mipChain;
};
static std::mutex g_qd3d12MipMutex;
static std::condition_variable g_qd3d12MipCv;
static std::deque<QD3D12MipBuildInput> g_qd3d12MipJobs;
static std::deque<QD3D12MipBuildResult> g_qd3d12MipCompleted;
static std::vector<std::thread> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::vector<uint8_t>>& 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<std::vector<uint8_t>>& 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<UINT>(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<UINT>(1u, (UINT)tex.width >> 1);
UINT mipH = std::max<UINT>(1u, (UINT)tex.height >> 1);
for (UINT mip = 1; mip < expectedMipLevels; ++mip)
{
const std::vector<uint8_t>& 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<UINT>(1u, mipW >> 1);
mipH = std::max<UINT>(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<QD3D12MipBuildResult> completed;
{
std::lock_guard<std::mutex> 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 = 2ull * 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, ID3D12GraphicsCommandList* commandList)
{
if (!tex.texture)
return;
ID3D12GraphicsCommandList* cl = commandList ? commandList : g_gl.cmdList.Get();
if (!cl)
return;
if (tex.compressed)
{
const UINT blockBytes = tex.compressedBlockBytes ? tex.compressedBlockBytes : QD3D12_CompressedTextureBlockBytes(tex.compressedInternalFormat);
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>((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;
cl->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;
cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
D3D12_RESOURCE_BARRIER toSrv{};
toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
toSrv.Transition.pResource = tex.texture.Get();
toSrv.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
toSrv.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
cl->ResourceBarrier(1, &toSrv);
tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
tex.gpuValid = true;
return;
}
std::vector<uint8_t> 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;
cl->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;
cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
D3D12_RESOURCE_BARRIER toSrv{};
toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
toSrv.Transition.pResource = tex.texture.Get();
toSrv.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
toSrv.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
toSrv.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
cl->ResourceBarrier(1, &toSrv);
tex.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_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<uint8_t>& 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<uint8_t>& 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<ID3D12Resource> 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<uint8_t> 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);
}
if (memcmp(g_gl.modelMatrix.m, m.m, sizeof(g_gl.modelMatrix.m)) == 0)
return;
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<GLVertex>& dst, const std::vector<GLVertex>& 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 GLVertex* QD3D12_AppendToQueuedBatch(QueuedBatch* batch, size_t outCount)
{
if (!batch || 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
{
assert(batch->firstVertex + batch->vertexCount == first);
batch->vertexCount += outCount;
}
return dst;
}
static QueuedBatch* QD3D12_PrepareImmediateBatch(GLenum mode, size_t n)
{
QD3D12_EnsureFrameOpen();
const bool arbProgramsActive = QD3D12ARB_IsActive();
const QD3D12PreparedImmediateTextures preparedTextures = QD3D12_PrepareImmediateTextures(arbProgramsActive);
BatchKey key = QD3D12_GetOrBuildImmediateBatchKey(mode, n, preparedTextures.boundTextures, preparedTextures.normalMapTex, preparedTextures.glowMapTex, preparedTextures.specularMapTex);
const size_t markerCursor = g_gl.queryMarkers.size();
if (!g_gl.queuedBatches.empty() &&
!g_gl.queuedBatches.back().gpuIndexed &&
BatchKeyEquals(g_gl.queuedBatches.back().key, key) &&
g_gl.queuedBatches.back().markerEnd == markerCursor)
{
return &g_gl.queuedBatches.back();
}
QueuedBatch newBatch{};
newBatch.key = key;
newBatch.markerBegin = markerCursor;
newBatch.markerEnd = markerCursor;
newBatch.firstVertex = 0;
newBatch.vertexCount = 0;
g_gl.queuedBatches.push_back(newBatch);
return &g_gl.queuedBatches.back();
}
static void FlushImmediate(GLenum mode, const GLVertex* src, size_t n)
{
if (!src || n == 0)
return;
QD3D12_EnsureFrameOpen();
const bool arbProgramsActive = QD3D12ARB_IsActive();
const QD3D12PreparedImmediateTextures preparedTextures = QD3D12_PrepareImmediateTextures(arbProgramsActive);
BatchKey key = QD3D12_GetOrBuildImmediateBatchKey(mode, n, preparedTextures.boundTextures, preparedTextures.normalMapTex, preparedTextures.glowMapTex, preparedTextures.specularMapTex);
const size_t markerCursor = g_gl.queryMarkers.size();
QueuedBatch* batch = nullptr;
if (!g_gl.queuedBatches.empty() &&
!g_gl.queuedBatches.back().gpuIndexed &&
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* {
return QD3D12_AppendToQueuedBatch(batch, outCount);
};
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<GLVertex> tess;
tess.reserve(n);
std::vector<GLVertex> 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 size_t count = batch.gpuIndexed ? (size_t)batch.gpuIndexCount : 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 = batch.gpuIndexed ? 0 : (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{};
if (!batch.gpuIndexed)
{
const GLVertex* verts = QD3D12_GetBatchVertices(batch);
vbAlloc = QD3D12_AllocUpload(vbBytes, 256);
memcpy(vbAlloc.cpu, verts, vbBytes);
}
UploadAlloc cbAlloc = QD3D12_AllocUpload(cbBytes, 256);
DrawConstants* dc = reinterpret_cast<DrawConstants*>(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<float>(batch.key.normalMapStrength, 0.0f, 4.0f);
const float tessDisplacementScale = 0.08f;
dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ?
ClampValue<float>(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<float>(batch.key.normalMapStrength, 0.0f, 4.0f);
dc->neuralPomPad[2] = ClampValue<float>(1.0f + (neuralStrength - 1.0f) * 0.65f, 1.0f, 3.0f);
dc->neuralPomPad[3] = ClampValue<float>(1.0f + (neuralStrength - 1.0f) * 0.45f, 1.0f, 2.5f);
memcpy(dc->currentColor, batch.key.currentColor, sizeof(dc->currentColor));
dc->vertexColorPad[0] = batch.key.useVertexColor;
dc->vertexColorPad[1] = 0.0f;
dc->vertexColorPad[2] = 0.0f;
dc->vertexColorPad[3] = 0.0f;
if (batch.key.useARBPrograms && batch.key.arbConstants)
{
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{};
if (batch.gpuIndexed)
{
vbv = batch.gpuVbv;
}
else
{
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);
if (batch.gpuIndexed)
{
g_gl.cmdList->IASetIndexBuffer(&batch.gpuIbv);
g_gl.cmdList->DrawIndexedInstanced(batch.gpuIndexCount, 1, 0, 0, 0);
}
else
{
g_gl.cmdList->IASetIndexBuffer(nullptr);
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<LONG>(leftGL, 0, (LONG)activeWidth);
r.right = ClampValue<LONG>(rightGL, 0, (LONG)activeWidth);
// OpenGL scissor is bottom-left origin, D3D12 is top-left origin.
r.top = ClampValue<LONG>((LONG)activeHeight - topGL, 0, (LONG)activeHeight);
r.bottom = ClampValue<LONG>((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<GLclampd>(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<GLclampd>(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.alphaFuncMapped = MapAlphaFunc(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<GLclampd>(zNear, 0.0, 1.0);
g_gl.depthRangeFar = ClampValue<GLclampd>(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;
QD3D12Window* owner = g_gl.frameOwner ? g_gl.frameOwner : g_currentWindow;
if (!owner)
return;
if (!g_gl.frameOpen && g_gl.queuedBatches.empty())
{
if (QD3D12_AllowsGamePresentationFeatures(*owner))
QD3D12_WaitForGPU();
return;
}
QD3D12Window* savedWindow = g_currentWindow;
g_currentWindow = owner;
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 = owner->frames[owner->frameIndex];
fr.fenceValue = signalValue;
QD3D12_CHECK(fr.cmdAlloc->Reset());
QD3D12_CHECK(g_gl.cmdList->Reset(fr.cmdAlloc.Get(), nullptr));
QD3D12_BindTargetsForCurrentPhase(*owner);
g_gl.frameOpen = true;
g_gl.frameOwner = owner;
g_currentWindow = savedWindow;
}
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);
}
}
}
void APIENTRY glBindTexture(GLenum, GLuint texture)
{
if (g_gl.boundTexture[g_gl.activeTextureUnit] == texture)
return;
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 (g_gl.currentNormalMapTexture == texture)
return;
if (texture != 0)
(void)QD3D12_EnsureTextureName(texture);
g_gl.currentNormalMapTexture = texture;
}
void APIENTRY glNormalMapTexture(GLuint texture)
{
glBindNormalMapTexture(texture);
}
void APIENTRY glNormalMapStrengthf(GLfloat strength)
{
const float clamped = (strength < 0.0f) ? 0.0f : strength;
if (g_gl.currentNormalMapStrength == clamped)
return;
g_gl.currentNormalMapStrength = clamped;
}
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<uint8_t>& outLatentRGBA16F)
{
outLatentRGBA16F.clear();
if (!weightsData || weightsBytes < 32 || !latentData || latentBytes <= 0)
return false;
const uint8_t* wb = reinterpret_cast<const uint8_t*>(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<const float*>(latentData);
uint16_t* dst = reinterpret_cast<uint16_t*>(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 (g_gl.currentNeuralPOMTexture == texture)
return;
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 (g_gl.currentGlowMapTexture == texture)
return;
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)
{
const float clamped = (strength < 0.0f) ? 0.0f : strength;
if (g_gl.currentGlowMapStrength == clamped)
return;
g_gl.currentGlowMapStrength = clamped;
}
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 (g_gl.currentSpecularMapTexture == texture)
return;
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 glTextureAverageColorQD3D12(GLuint texture, GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
if (texture == 0)
return;
TextureResource& tex = QD3D12_EnsureTextureName(texture);
tex.averageColor[0] = ClampValue<float>(r, 0.0f, 1.0f);
tex.averageColor[1] = ClampValue<float>(g, 0.0f, 1.0f);
tex.averageColor[2] = ClampValue<float>(b, 0.0f, 1.0f);
tex.averageColor[3] = ClampValue<float>(a, 0.0f, 1.0f);
tex.cpuGeneration++;
}
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);
}
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<UINT>(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<GLfloat>(param, 1.0f, 4.0f);
break;
#endif
#ifdef GL_ALPHA_SCALE
case GL_ALPHA_SCALE:
g_gl.texAlphaScale[unit] = ClampValue<GLfloat>(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<UINT>(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<const uint8_t*>(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<uint8_t> 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<uint8_t> 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<uint8_t> rgba;
if (!QD3D12_ReadFramebufferRegionRGBA8(x, y, width, height, rgba) || rgba.empty())
{
memset(data, 0, dstBytes);
return;
}
std::vector<uint8_t> 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<GLint>(1, tex.width >> level);
const GLint mipHeight = std::max<GLint>(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 || !g_currentWindow->swapChain)
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);
HRESULT resizeHr = g_currentWindow->swapChain->ResizeBuffers(
QD3D12_FrameCount,
width,
height,
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
);
if (FAILED(resizeHr))
{
QD3D12_Log("ResizeBuffers failed 0x%08X; recreating swapchain.", (unsigned)resizeHr);
g_currentWindow->swapChain.Reset();
g_currentWindow->width = width;
g_currentWindow->height = height;
QD3D12_SelectRenderResolution(*g_currentWindow, width, height);
QD3D12_CreateSwapChainForWindow(*g_currentWindow);
}
g_currentWindow->width = width;
g_currentWindow->height = height;
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();
}
static void QD3D12_RecreateCurrentWindowSwapChainForFrameGenerationChange(bool enableFrameGeneration)
{
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_STREAMLINE)
if (!enableFrameGeneration)
QD3D12_SetDLSSFrameGenerationOff();
#endif
#if defined(QD3D12_ENABLE_FFX)
QD3D12_DestroyFfxUpscaleContext();
#endif
QD3D12_ReleaseWindowSizeResources(*g_currentWindow);
g_currentWindow->swapChain.Reset();
#if defined(QD3D12_ENABLE_STREAMLINE)
QD3D12_SetDLSSGFeatureLoaded(enableFrameGeneration);
#endif
QD3D12_CreateSwapChainForWindow(*g_currentWindow);
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;
#if defined(QD3D12_ENABLE_STREAMLINE)
g_gl.streamlineFrameToken = nullptr;
g_gl.streamlineFrameTokenSerial = UINT64_MAX;
g_gl.streamlineConstantsSerial = UINT64_MAX;
#endif
QD3D12_UpdateViewportState();
QD3D12_Log("DLSS_G swap-chain recreated with frame generation %s.", enableFrameGeneration ? "enabled" : "disabled");
}
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<GLclampd>(zmin, 0.0, 1.0);
g_gl.depthBoundsMax = ClampValue<GLclampd>(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>((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)
{
const uint8_t* resolved = QD3D12_ResolveArrayPointer(pointer);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)pointer : 0;
if (g_gl.normalArray.size == 3 &&
g_gl.normalArray.type == type &&
g_gl.normalArray.stride == stride &&
g_gl.normalArray.ptr == resolved &&
g_gl.normalArray.buffer == buffer &&
g_gl.normalArray.offset == offset)
return;
QD3D12_CaptureArrayPointer(g_gl.normalArray, 3, type, stride, pointer);
}
void APIENTRY glTangentPointer(GLenum type, GLsizei stride, const void* pointer)
{
const uint8_t* resolved = QD3D12_ResolveArrayPointer(pointer);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)pointer : 0;
if (g_gl.tangentArray.size == 3 &&
g_gl.tangentArray.type == type &&
g_gl.tangentArray.stride == stride &&
g_gl.tangentArray.ptr == resolved &&
g_gl.tangentArray.buffer == buffer &&
g_gl.tangentArray.offset == offset)
return;
QD3D12_CaptureArrayPointer(g_gl.tangentArray, 3, type, stride, pointer);
}
void APIENTRY glBinormalPointer(GLenum type, GLsizei stride, const void* pointer)
{
const uint8_t* resolved = QD3D12_ResolveArrayPointer(pointer);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)pointer : 0;
if (g_gl.bitangentArray.size == 3 &&
g_gl.bitangentArray.type == type &&
g_gl.bitangentArray.stride == stride &&
g_gl.bitangentArray.ptr == resolved &&
g_gl.bitangentArray.buffer == buffer &&
g_gl.bitangentArray.offset == offset)
return;
QD3D12_CaptureArrayPointer(g_gl.bitangentArray, 3, type, stride, 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:
if (g_gl.vertexArray.enabled)
return;
g_gl.vertexArray.enabled = true;
break;
case GL_NORMAL_ARRAY:
if (g_gl.normalArray.enabled)
return;
g_gl.normalArray.enabled = true;
break;
case GL_TANGENT_ARRAY_QD3D12:
if (g_gl.tangentArray.enabled)
return;
g_gl.tangentArray.enabled = true;
break;
case GL_BINORMAL_ARRAY_QD3D12:
if (g_gl.bitangentArray.enabled)
return;
g_gl.bitangentArray.enabled = true;
break;
case GL_COLOR_ARRAY:
if (g_gl.colorArray.enabled)
return;
g_gl.colorArray.enabled = true;
break;
case GL_TEXTURE_COORD_ARRAY:
if (g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled)
return;
g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled = true;
break;
default:
break;
}
}
void APIENTRY glDisableClientState(GLenum array)
{
switch (array)
{
case GL_VERTEX_ARRAY:
if (!g_gl.vertexArray.enabled)
return;
g_gl.vertexArray.enabled = false;
break;
case GL_NORMAL_ARRAY:
if (!g_gl.normalArray.enabled)
return;
g_gl.normalArray.enabled = false;
break;
case GL_TANGENT_ARRAY_QD3D12:
if (!g_gl.tangentArray.enabled)
return;
g_gl.tangentArray.enabled = false;
break;
case GL_BINORMAL_ARRAY_QD3D12:
if (!g_gl.bitangentArray.enabled)
return;
g_gl.bitangentArray.enabled = false;
break;
case GL_COLOR_ARRAY:
if (!g_gl.colorArray.enabled)
return;
g_gl.colorArray.enabled = false;
break;
case GL_TEXTURE_COORD_ARRAY:
if (!g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled)
return;
g_gl.texCoordArray[g_gl.clientActiveTextureUnit].enabled = false;
break;
default:
break;
}
}
void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr)
{
const uint8_t* resolved = QD3D12_ResolveArrayPointer(ptr);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)ptr : 0;
if (g_gl.vertexArray.size == size &&
g_gl.vertexArray.type == type &&
g_gl.vertexArray.stride == stride &&
g_gl.vertexArray.ptr == resolved &&
g_gl.vertexArray.buffer == buffer &&
g_gl.vertexArray.offset == offset)
return;
QD3D12_CaptureArrayPointer(g_gl.vertexArray, size, type, stride, ptr);
}
void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr)
{
const uint8_t* resolved = QD3D12_ResolveArrayPointer(ptr);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)ptr : 0;
if (g_gl.colorArray.size == size &&
g_gl.colorArray.type == type &&
g_gl.colorArray.stride == stride &&
g_gl.colorArray.ptr == resolved &&
g_gl.colorArray.buffer == buffer &&
g_gl.colorArray.offset == offset)
return;
QD3D12_CaptureArrayPointer(g_gl.colorArray, size, type, stride, ptr);
}
void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* ptr)
{
auto& tc = g_gl.texCoordArray[g_gl.clientActiveTextureUnit];
const uint8_t* resolved = QD3D12_ResolveArrayPointer(ptr);
const GLuint buffer = g_gl.boundArrayBuffer;
const size_t offset = buffer != 0 ? (size_t)ptr : 0;
if (tc.size == size &&
tc.type == type &&
tc.stride == stride &&
tc.ptr == resolved &&
tc.buffer == buffer &&
tc.offset == offset)
return;
QD3D12_CaptureArrayPointer(tc, size, type, stride, ptr);
}
void APIENTRY glArrayElement(GLint i)
{
GLVertex& v = g_gl.immediateVerts.Push();
QD3D12_FetchArrayVertex(i, v);
}
static bool QD3D12_ArrayUsesOnlyBuffer(const GLState::ClientArrayState& array, GLuint buffer)
{
return !array.enabled || array.buffer == buffer;
}
static bool QD3D12_CanPackCurrentVertexBuffer(GLuint vertexBuffer)
{
if (vertexBuffer == 0 || !g_gl.vertexArray.enabled || g_gl.vertexArray.buffer != vertexBuffer)
return false;
if (g_gl.vertexArray.type != GL_FLOAT || g_gl.vertexArray.size < 3)
return false;
if (!QD3D12_ArrayUsesOnlyBuffer(g_gl.normalArray, vertexBuffer) ||
!QD3D12_ArrayUsesOnlyBuffer(g_gl.tangentArray, vertexBuffer) ||
!QD3D12_ArrayUsesOnlyBuffer(g_gl.bitangentArray, vertexBuffer) ||
!QD3D12_ArrayUsesOnlyBuffer(g_gl.colorArray, vertexBuffer))
return false;
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
{
if (!QD3D12_ArrayUsesOnlyBuffer(g_gl.texCoordArray[unit], vertexBuffer))
return false;
}
return true;
}
static uint64_t QD3D12_HashClientArrayLayout(const GLState::ClientArrayState& array, uint64_t hash)
{
hash ^= (uint64_t)array.enabled + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
hash ^= (uint64_t)array.size + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
hash ^= (uint64_t)array.type + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
hash ^= (uint64_t)array.stride + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
hash ^= (uint64_t)array.buffer + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
hash ^= (uint64_t)array.offset + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
return hash;
}
static uint64_t QD3D12_CurrentClientArrayLayoutHash()
{
uint64_t hash = 1469598103934665603ull;
hash = QD3D12_HashClientArrayLayout(g_gl.vertexArray, hash);
hash = QD3D12_HashClientArrayLayout(g_gl.normalArray, hash);
hash = QD3D12_HashClientArrayLayout(g_gl.tangentArray, hash);
hash = QD3D12_HashClientArrayLayout(g_gl.bitangentArray, hash);
hash = QD3D12_HashClientArrayLayout(g_gl.colorArray, hash);
for (UINT unit = 0; unit < QD3D12_MaxTextureUnits; ++unit)
hash = QD3D12_HashClientArrayLayout(g_gl.texCoordArray[unit], hash);
return hash;
}
static bool QD3D12_EnsurePackedVertexBuffer(GLBufferObject& bo)
{
if (bo.usage == GL_STREAM_DRAW_ARB || bo.data.empty())
return false;
if (!QD3D12_CanPackCurrentVertexBuffer(bo.id))
return false;
const GLsizei strideValue = g_gl.vertexArray.stride;
const size_t vertexTypeSize = QD3D12_TypeSize(g_gl.vertexArray.type);
const size_t vertexStride = strideValue ? (size_t)strideValue : (size_t)g_gl.vertexArray.size * vertexTypeSize;
if (vertexStride == 0 || g_gl.vertexArray.offset >= bo.data.size())
return false;
const size_t vertexCount = (bo.data.size() - g_gl.vertexArray.offset) / vertexStride;
const size_t packedBytes = vertexCount * sizeof(GLVertex);
if (vertexCount == 0 || packedBytes == 0)
return false;
const uint64_t layoutHash = QD3D12_CurrentClientArrayLayoutHash();
if (bo.packedVertexResource &&
bo.packedVertexMapped &&
bo.packedVertexRevision == bo.revision &&
bo.packedVertexLayoutHash == layoutHash &&
bo.packedVertexCount == vertexCount &&
bo.packedVertexBytes >= packedBytes)
{
return true;
}
QD3D12_ResetPackedVertexBuffer(bo);
if (!g_gl.device)
return false;
D3D12_HEAP_PROPERTIES hp{};
hp.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC rd{};
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
rd.Width = (UINT64)packedBytes;
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_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&bo.packedVertexResource)));
bo.packedVertexGpuAddress = bo.packedVertexResource->GetGPUVirtualAddress();
bo.packedVertexBytes = packedBytes;
bo.packedVertexCount = vertexCount;
QD3D12_CHECK(bo.packedVertexResource->Map(0, nullptr, reinterpret_cast<void**>(&bo.packedVertexMapped)));
GLVertex* packed = reinterpret_cast<GLVertex*>(bo.packedVertexMapped);
for (size_t i = 0; i < vertexCount; ++i)
QD3D12_FetchArrayVertex((GLint)i, packed[i]);
bo.packedVertexRevision = bo.revision;
bo.packedVertexLayoutHash = layoutHash;
return true;
}
static DXGI_FORMAT QD3D12_IndexFormatFromGL(GLenum type)
{
switch (type)
{
case GL_UNSIGNED_INT: return DXGI_FORMAT_R32_UINT;
case GL_UNSIGNED_SHORT: return DXGI_FORMAT_R16_UINT;
default: return DXGI_FORMAT_UNKNOWN;
}
}
static size_t QD3D12_IndexSizeFromGL(GLenum type)
{
switch (type)
{
case GL_UNSIGNED_INT: return sizeof(GLuint);
case GL_UNSIGNED_SHORT: return sizeof(GLushort);
case GL_UNSIGNED_BYTE: return sizeof(GLubyte);
default: return 0;
}
}
static bool QD3D12_TryQueueBufferedDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
{
if (mode != GL_TRIANGLES || type == GL_UNSIGNED_BYTE)
return false;
if (QD3D12ARB_IsActive())
return false;
GLBufferObject* vertexBo = QD3D12_GetBuffer(g_gl.vertexArray.buffer);
GLBufferObject* indexBo = QD3D12_GetBuffer(g_gl.boundElementArrayBuffer);
if (!vertexBo || !indexBo || !indexBo->resource || !indexBo->mappedGpu)
return false;
if (!QD3D12_EnsurePackedVertexBuffer(*vertexBo))
return false;
const size_t indexSize = QD3D12_IndexSizeFromGL(type);
const size_t indexOffset = (g_gl.boundElementArrayBuffer != 0) ? (size_t)indices : 0;
const size_t indexBytes = (size_t)count * indexSize;
if (indexSize == 0 || indexOffset > indexBo->gpuBytes || indexBytes > (indexBo->gpuBytes - indexOffset))
return false;
QueuedBatch* batch = QD3D12_PrepareImmediateBatch(mode, (size_t)count);
if (!batch || batch->vertexCount != 0 || batch->gpuIndexed)
{
QueuedBatch newBatch{};
newBatch.key = batch ? batch->key : BuildCurrentBatchKey(mode, nullptr, nullptr, nullptr);
newBatch.markerBegin = g_gl.queryMarkers.size();
newBatch.markerEnd = newBatch.markerBegin;
g_gl.queuedBatches.push_back(newBatch);
batch = &g_gl.queuedBatches.back();
}
batch->gpuIndexed = true;
batch->gpuVertexResource = vertexBo->packedVertexResource;
batch->gpuIndexResource = indexBo->resource;
batch->gpuIndexCount = (UINT)count;
batch->vertexCount = (size_t)count;
batch->gpuVbv.BufferLocation = vertexBo->packedVertexGpuAddress;
batch->gpuVbv.SizeInBytes = (UINT)vertexBo->packedVertexBytes;
batch->gpuVbv.StrideInBytes = sizeof(GLVertex);
batch->gpuIbv.BufferLocation = indexBo->gpuAddress + indexOffset;
batch->gpuIbv.SizeInBytes = (UINT)indexBytes;
batch->gpuIbv.Format = QD3D12_IndexFormatFromGL(type);
return true;
}
void APIENTRY glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
{
if (count <= 0)
return;
if (type != GL_UNSIGNED_INT && type != GL_UNSIGNED_SHORT && type != GL_UNSIGNED_BYTE)
{
g_gl.lastError = GL_INVALID_ENUM;
return;
}
const void* resolvedIndices = QD3D12_ResolveElementPointer(indices, type, count);
if (!resolvedIndices)
{
g_gl.lastError = GL_INVALID_OPERATION;
return;
}
g_gl.currentPrim = mode;
if (QD3D12_TryQueueBufferedDrawElements(mode, count, type, indices))
return;
if (mode == GL_TRIANGLES)
{
QueuedBatch* batch = QD3D12_PrepareImmediateBatch(mode, static_cast<size_t>(count));
GLVertex* outVerts = QD3D12_AppendToQueuedBatch(batch, static_cast<size_t>(count));
if (!outVerts)
return;
switch (type)
{
case GL_UNSIGNED_INT:
{
const GLuint* idx = static_cast<const GLuint*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
return;
}
case GL_UNSIGNED_SHORT:
{
const GLushort* idx = static_cast<const GLushort*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
return;
}
case GL_UNSIGNED_BYTE:
{
const GLubyte* idx = static_cast<const GLubyte*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
return;
}
}
}
GLVertex* outVerts = g_gl.immediateVerts.ResizeForWrite(static_cast<size_t>(count));
if (!outVerts)
return;
switch (type)
{
case GL_UNSIGNED_INT:
{
const GLuint* idx = static_cast<const GLuint*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
{
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
}
break;
}
case GL_UNSIGNED_SHORT:
{
const GLushort* idx = static_cast<const GLushort*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
{
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
}
break;
}
case GL_UNSIGNED_BYTE:
{
const GLubyte* idx = static_cast<const GLubyte*>(resolvedIndices);
for (GLsizei i = 0; i < count; ++i)
{
QD3D12_FetchArrayVertex(static_cast<GLint>(idx[i]), outVerts[i]);
}
break;
}
default:
g_gl.lastError = GL_INVALID_ENUM;
g_gl.immediateVerts.Clear();
return;
}
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<HDC, QD3D12Window*> g_qd3d12DcToWindow;
static std::unordered_map<HGLRC, QD3D12GLContext*> g_qd3d12Contexts;
static std::unordered_map<HPBUFFERARB, QD3D12Pbuffer*> 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<HDC, QD3D12Window*>::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<HGLRC>(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<HDC>(pb);
if (!QD3D12_InitPbufferWindow(*pb, iWidth, iHeight))
{
delete pb;
return nullptr;
}
HPBUFFERARB handle = reinterpret_cast<HPBUFFERARB>(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 },
{ "glSetScreenSpaceDecalsQD3D12",(PROC)glSetScreenSpaceDecalsQD3D12 },
{ "glDrawScreenSpaceDecalsQD3D12",(PROC)glDrawScreenSpaceDecalsQD3D12 },
{ "glSetScreenSpaceParticleLightsQD3D12",(PROC)glSetScreenSpaceParticleLightsQD3D12 },
{ "glDrawScreenSpaceParticlesQD3D12",(PROC)glDrawScreenSpaceParticlesQD3D12 },
{ "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 },
{ "QD3D12_SetFrameGenerationMultiplier", (PROC)QD3D12_SetFrameGenerationMultiplier },
{ "QD3D12_GetFrameGenerationMultiplier", (PROC)QD3D12_GetFrameGenerationMultiplier },
{ "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);
TextureResource* readLightingTex = g_lightingTexture[g_lightingReadIndex];
if (readLightingTex && readLightingTex->texture)
{
QD3D12_TransitionResource(
cl,
readLightingTex->texture.Get(),
g_lightingTextureState[g_lightingReadIndex],
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
g_lightingTextureState[g_lightingReadIndex] = 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;
if (QD3D12_IsNativeEditorPreviewWindow(*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();
const UINT writeLightingIndex = g_lightingWriteIndex % QD3D12_LightingTextureCount;
const UINT previousLightingIndex = writeLightingIndex ^ 1u;
TextureResource* writeLightingTex = QD3D12_EnsureLightingTexture(width, height, writeLightingIndex);
(void)QD3D12_EnsureLightingTexture(width, height, previousLightingIndex);
const UINT readLightingIndex = writeLightingIndex;
TextureResource* readLightingTex = g_lightingTexture[readLightingIndex];
if (!writeLightingTex || !writeLightingTex->texture || !readLightingTex || !readLightingTex->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;
QD3D12_FlushQueuedBatches();
QD3D12_ResolveGBufferForCurrentFrame(*window);
QD3D12_DrawQueuedScreenSpaceParticleEmissive(tlas);
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[writeLightingIndex] != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
{
QD3D12_TransitionResource(
cl,
writeLightingTex->texture.Get(),
g_lightingTextureState[writeLightingIndex],
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
g_lightingTextureState[writeLightingIndex] = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}
QD3D12_ExecuteMainCommandListForRaytracingHandoff(*window);
cl = g_gl.cmdList.Get();
QD3D12_UpdateCameraInfoFromCurrentMatrices();
const bool useDLSSRayReconstruction = QD3D12_CanUseDLSSRayReconstructionForLighting(*window);
const uint32_t activeMaxBounces = std::max<uint32_t>(1u, g_gl.pathTracingMaxBounces);
const uint32_t raySpp = useDLSSRayReconstruction
? std::max<uint32_t>(1u, g_gl.pathTracingSamplesPerPixel)
: std::max<uint32_t>(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 = writeLightingTex->texture.Get();
pass.outputFormat = writeLightingTex->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[writeLightingIndex] = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
g_lightingTextureValid[writeLightingIndex] = true;
g_lightingReadIndex = readLightingIndex;
g_lightingWriteIndex = previousLightingIndex;
QD3D12_RunRayAIDenoiseIfEnabled(cl, *window, writeLightingTex->texture.Get());
if (!g_gl.screenSpaceParticleBatches.empty())
{
// The particle pass is a blended graphics pass, so it needs a render-target
// scene color surface. Keep particle frames on the composited scene-color
// path instead of feeding the raw lighting texture straight to RR/upscale.
g_gl.blockLightingUpscaleInputThisFrame = true;
}
// If the denoiser changes the written texture state, restore it to a
// shader-readable state before this slot becomes the next read target.
// restore the texture to a shader-readable state for the post/upscale pass.
if (g_lightingTextureState[writeLightingIndex] != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
{
QD3D12_TransitionResource(
cl,
writeLightingTex->texture.Get(),
g_lightingTextureState[writeLightingIndex],
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
g_lightingTextureState[writeLightingIndex] = 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,
readLightingTex->srvGpu,
CurrentResolvedSceneColorRTV(),
viewport,
scissor,
0.0f,
false))
{
QD3D12_PostFullscreenPass(
cl,
g_gl.postCopyPSO.Get(),
readLightingTex->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_DrawQueuedScreenSpaceParticles(tlas);
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)
{
if (g_gl.currentGeometryFlag == flag)
return;
g_gl.currentGeometryFlag = flag;
}
void APIENTRY glMotionObjectIdui(GLuint objectId)
{
g_gl.currentMotionObjectId = objectId;
}
void APIENTRY glSurfaceRoughnessf(GLfloat roughness)
{
g_gl.currentSurfaceRoughness = ClampValue<float>(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_SetFrameGenerationMultiplier(int multiplier)
{
const uint32_t clamped = (uint32_t)ClampValue<int>(multiplier, 0, 4);
const bool wasEnabled = g_gl.frameGenerationMultiplier >= 2;
const bool willBeEnabled = clamped >= 2;
if (g_gl.frameGenerationMultiplier == clamped)
return;
g_gl.frameGenerationMultiplier = clamped;
g_gl.motionHistoryReset = true;
if (wasEnabled != willBeEnabled)
QD3D12_RecreateCurrentWindowSwapChainForFrameGenerationChange(willBeEnabled);
}
int QD3D12_GetFrameGenerationMultiplier(void)
{
return (int)g_gl.frameGenerationMultiplier;
}
int QD3D12_GetActiveFrameGenerationMultiplier(void)
{
#if defined(QD3D12_ENABLE_STREAMLINE)
if (g_qd3d12Sl.dlssGActive && g_qd3d12Sl.dlssGActiveFrameMultiplier >= 2)
return (int)g_qd3d12Sl.dlssGActiveFrameMultiplier;
#endif
return 1;
}
const char* QD3D12_GetAdapterName(void)
{
return g_gl.adapterName.empty() ? nullptr : g_gl.adapterName.c_str();
}
int QD3D12_GetVideoMemoryInfo(uint64_t* usageBytes, uint64_t* budgetBytes, uint64_t* dedicatedBytes)
{
if (usageBytes)
*usageBytes = 0;
if (budgetBytes)
*budgetBytes = 0;
if (dedicatedBytes)
*dedicatedBytes = g_gl.dedicatedVideoMemory;
if (!g_gl.adapter)
return 0;
DXGI_QUERY_VIDEO_MEMORY_INFO info{};
if (FAILED(g_gl.adapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info)))
return 0;
if (usageBytes)
*usageBytes = info.CurrentUsage;
if (budgetBytes)
*budgetBytes = info.Budget;
return 1;
}
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)
{
(void)enabled;
const bool newValue = 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 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<float>(sharpness, 0.0f, 1.0f);
}
static float QD3D12_ClampToneMapBrightness(float brightness)
{
if (!std::isfinite(brightness))
return 1.0f;
return ClampValue<float>(brightness, 0.0f, 8.0f);
}
void QD3D12_SetToneMapBrightness(float brightness)
{
const float clamped = QD3D12_ClampToneMapBrightness(brightness);
if (g_gl.toneMapBrightness == clamped)
return;
g_gl.toneMapBrightness = clamped;
}
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)
{
(void)enabled;
const bool newValue = 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<uint32_t>(1u, std::min<uint32_t>(samplesPerPixel ? samplesPerPixel : 1u, 8u));
g_gl.pathTracingFallbackSamplesPerPixel = std::max<uint32_t>(g_gl.pathTracingSamplesPerPixel, 2u);
g_gl.pathTracingMaxBounces = std::max<uint32_t>(1u, std::min<uint32_t>(maxBounces ? maxBounces : 1u, 4u));
g_gl.motionHistoryReset = true;
}
void QD3D12_SetPathTracingFallbackSamples(uint32_t samplesPerPixel)
{
g_gl.pathTracingFallbackSamplesPerPixel = std::max<uint32_t>(1u, std::min<uint32_t>(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* worldToView,
const float* viewToWorld,
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 (worldToView)
memcpy(g_gl.cameraState.worldToView.m, worldToView, sizeof(float) * 16);
if (viewToWorld)
memcpy(g_gl.cameraState.viewToWorld.m, viewToWorld, 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;
auto it = g_gl.buffers.find(id);
if (it != g_gl.buffers.end())
QD3D12_ResetBufferResource(it->second);
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;
}
QD3D12_UpdateBufferResource(*bo, target, size, data, GL_STATIC_DRAW_ARB);
bo->storageFlags = flags;
}
void APIENTRY glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage)
{
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;
}
QD3D12_UpdateBufferResource(*bo, target, size, data, usage);
}
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);
++bo->revision;
QD3D12_ResetPackedVertexBuffer(*bo);
if (bo->mappedGpu)
memcpy(bo->mappedGpu + (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;
if (bo->mappedAccess & GL_MAP_WRITE_BIT)
{
++bo->revision;
QD3D12_ResetPackedVertexBuffer(*bo);
if (bo->mappedGpu && bo->mappedLength > 0)
memcpy(bo->mappedGpu + (size_t)bo->mappedOffset, bo->data.data() + (size_t)bo->mappedOffset, (size_t)bo->mappedLength);
}
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<const GLubyte*>(lists);
for (GLsizei i = 0; i < n; ++i)
glCallList(g_qd3d12ListBaseCompat + (GLuint)p[i]);
break;
}
case GL_BYTE:
{
const GLbyte* p = static_cast<const GLbyte*>(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<const GLushort*>(lists);
for (GLsizei i = 0; i < n; ++i)
glCallList(g_qd3d12ListBaseCompat + (GLuint)p[i]);
break;
}
case GL_SHORT:
{
const GLshort* p = static_cast<const GLshort*>(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<const GLuint*>(lists);
for (GLsizei i = 0; i < n; ++i)
glCallList(g_qd3d12ListBaseCompat + p[i]);
break;
}
case GL_INT:
{
const GLint* p = static_cast<const GLint*>(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<QD3D12AttribState> 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<uint8_t>& outRGBA)
{
if (width <= 0 || height <= 0 || !pixels)
return false;
if (type != GL_UNSIGNED_BYTE)
return false;
const uint8_t* src = static_cast<const uint8_t*>(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<uint8_t> 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<glRaytracingVertex_t>& 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<glRaytracingVertex_t> drawVertexes;
ExpandImmediateSlow(g_gl.currentPrim, g_gl.immediateVerts, drawVertexes);
std::vector<unsigned int> 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;
meshDesc.averageColor[0] = meshDesc.averageColor[1] = meshDesc.averageColor[2] = meshDesc.averageColor[3] = 1.0f;
TextureResource* diffuseTexture = QD3D12_FindTextureResource(g_gl.boundTexture[0]);
if (diffuseTexture)
memcpy(meshDesc.averageColor, diffuseTexture->averageColor, sizeof(meshDesc.averageColor));
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);
glRaytracingSetMeshAverageColor(
(glRaytracingMeshHandle_t)meshHandle,
meshDesc.averageColor[0],
meshDesc.averageColor[1],
meshDesc.averageColor[2],
meshDesc.averageColor[3]);
}
}
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);
}
}