Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Documents/Build-PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,39 @@ not bring `__super` with it, and the engine uses that keyword in ~180 files.
Don't pin the Visual Studio generator. `windows-latest` moved to VS 2026 mid-2026; naming a
version only moves the breakage to the next image bump. CMake picks the newest it finds.

### Every vertex input's semantic is `TEXCOORD<location>`

Windows is the one platform that compiles our HLSL *as HLSL* — dxc straight to DXIL, with
the semantics we wrote. Linux and macOS launder them through SPIR-V, where semantics do not
survive. Only D3D12 ever sees the names, and it is the one backend that checks them.

SDL_GPU has no semantic to bind by: `SDL_GPUVertexAttribute` carries a *location*, because
that is all SPIR-V and MSL need. Its D3D12 backend therefore invents one, stamping every
`D3D12_INPUT_ELEMENT_DESC` with `SemanticName = "TEXCOORD"` and `SemanticIndex = location`
(`D3D12_INTERNAL_ConvertVertexInputState`). `CreateInputLayout` then rejects a layout that
does not name every element the shader reads — so `float3 pos : POSITION` cannot be bound:

```
D3D12 ERROR: ID3D12Device::CreateInputLayout: The provided input signature expects to read
an element with SemanticName/Index: 'POSITION'/0, but the declaration doesn't provide a
matching name. [ STATE_CREATION ERROR #65: CREATEINPUTLAYOUT_MISSINGELEMENT]
```

`SDL_CreateGPUGraphicsPipeline` reports that as a bare `E_INVALIDARG` (0x80070057) with no
mention of semantics, so **turn on the D3D12 debug layer before believing any pipeline
error** — the useful message only exists there.

So: a vertex shader input takes `TEXCOORD<its own location>`, never the semantic the data
means. `#if`-conditional layouts renumber with the `#if` (see `object3dx.vert.hlsl`, where
a rigid lod carries no blend weights and everything after them shifts down one). Nothing
else is constrained — interpolants between our own vertex and fragment stages are matched
by name within the pair, and keep whatever reads best.

Resource registers have a matching convention, and this tree already follows it: vertex
uniforms at `b0, space1`, fragment textures/samplers at `t#/s#, space2`, fragment uniforms
at `b0, space3`. Getting one wrong fails pipeline creation the same opaque way, because the
root signature SDL builds will not cover the shader's bindings.

## One source list per module

Every module's `CMakeLists.txt` used to have a `WIN32` branch feeding it a different set of
Expand Down
3 changes: 2 additions & 1 deletion Render/SDLCloudShadowRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ SDLCloudShadowRenderer::SDLCloudShadowRenderer(cSDLRenderDevice* owner, SDL_GPUD
createSampler();
whiteTexture_ = createSolidGPUTexture(device_, 0xffffffffu);
if(createShaders())
fprintf(stderr, "SDLCloudShadowRenderer: cloud shadow pipeline ready\n");
// Shaders only; pipelineFor() builds the pipelines on demand. See SDLGrassRenderer.
fprintf(stderr, "SDLCloudShadowRenderer: cloud shadow shaders ready\n");
}

SDLCloudShadowRenderer::~SDLCloudShadowRenderer()
Expand Down
3 changes: 2 additions & 1 deletion Render/SDLEnvironmentEarthRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ SDLEnvironmentEarthRenderer::SDLEnvironmentEarthRenderer(cSDLRenderDevice* owner
createSampler();
whiteTexture_ = createSolidGPUTexture(device_, 0xffffffffu);
if(createShaders())
fprintf(stderr, "SDLEnvironmentEarthRenderer: environment earth pipeline ready\n");
// Shaders only; pipelineFor() builds the pipelines on demand. See SDLGrassRenderer.
fprintf(stderr, "SDLEnvironmentEarthRenderer: environment earth shaders ready\n");
}

SDLEnvironmentEarthRenderer::~SDLEnvironmentEarthRenderer()
Expand Down
5 changes: 4 additions & 1 deletion Render/SDLGrassRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ SDLGrassRenderer::SDLGrassRenderer(cSDLRenderDevice* owner, SDL_GPUDevice* devic
createSamplers();
whiteTexture_ = createSolidGPUTexture(device_, 0xffffffffu);
if(createShaders())
fprintf(stderr, "SDLGrassRenderer: grass pipeline ready\n");
// Shaders only: the pipelines themselves are built on demand, per vertex stride,
// in pipelineFor(). Do not report them as ready here -- a pipeline that fails to
// create does so long after this line, and saying "pipeline" reads as if it had not.
fprintf(stderr, "SDLGrassRenderer: grass shaders ready\n");
}

SDLGrassRenderer::~SDLGrassRenderer()
Expand Down
3 changes: 2 additions & 1 deletion Render/SDLPostEffectRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ SDLPostEffectRenderer::SDLPostEffectRenderer(SDL_GPUDevice* device, SDL_Window*
{
createSamplers();
if(createShaders())
fprintf(stderr, "SDLPostEffectRenderer: post-effect pipelines ready\n");
// Shaders only; the pipelines are built on demand. See SDLGrassRenderer.
fprintf(stderr, "SDLPostEffectRenderer: post-effect shaders ready\n");
}

SDLPostEffectRenderer::~SDLPostEffectRenderer()
Expand Down
26 changes: 26 additions & 0 deletions Render/SDLShaders/ShaderBlob.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ inline constexpr SDL_GPUShaderFormat kShaderFormat = SDL_GPU_SHADERFORMAT_SP
inline constexpr const char* kShaderEntryPoint = "main";
#endif

// ---------------------------------------------------------------------------------------
// Why every vertex input in SDLShaders/*.vert.hlsl is a TEXCOORD
// ---------------------------------------------------------------------------------------
// SDL_GPU describes a vertex attribute by *location* alone -- SDL_GPUVertexAttribute has no
// semantic field, because SPIR-V and MSL have no semantics to fill it with. D3D12 does, so
// its backend has to invent one: D3D12_INTERNAL_ConvertVertexInputState stamps every
// D3D12_INPUT_ELEMENT_DESC with SemanticName = "TEXCOORD" and SemanticIndex = location.
//
// ID3D12Device::CreateInputLayout then rejects any layout that does not name every element
// the vertex shader's input signature reads. So a shader declaring `float3 pos : POSITION`
// fails pipeline creation outright:
//
// CREATEINPUTLAYOUT_MISSINGELEMENT: The provided input signature expects to read an
// element with SemanticName/Index: 'POSITION'/0, but the declaration doesn't provide a
// matching name.
//
// which surfaces from SDL_CreateGPUGraphicsPipeline as a bare E_INVALIDARG (0x80070057).
// The rule is therefore: a vertex shader input's semantic is TEXCOORD<its location>, never
// what the data means. The `#if`-conditional layouts (object3dx) renumber accordingly.
//
// Only the DXIL path cares. Locations in SPIR-V and MSL come from declaration order, which
// the naming does not affect, so the same source keeps its bindings on Vulkan and Metal.
//
// This constrains *inputs* only. Interpolants between the vertex and fragment stages are
// matched by name within our own shader pairs, and keep the semantic that reads best.

// Fill in the fields of an SDL_GPUShaderCreateInfo that are the same for every shader we
// build: the bytecode, its format, and its entry point. The caller still sets the stage
// and the resource counts, which differ per shader.
Expand Down
10 changes: 6 additions & 4 deletions Render/SDLShaders/cloudshadow.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ cbuffer Constants : register(b0, space1)
struct VSInput
{
// sVertexXYZDT2, stride 32.
float3 Position : POSITION; // offset 0, world space
float4 Color : COLOR0; // offset 12, D3DCOLOR -> UBYTE4_NORM (b,g,r,a)
float2 UV0 : TEXCOORD0; // offset 16, the first scroll
float2 UV1 : TEXCOORD1; // offset 24, the second
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // offset 0, world space
float4 Color : TEXCOORD1; // offset 12, D3DCOLOR -> UBYTE4_NORM (b,g,r,a)
float2 UV0 : TEXCOORD2; // offset 16, the first scroll
float2 UV1 : TEXCOORD3; // offset 24, the second
};

struct VSOutput
Expand Down
8 changes: 5 additions & 3 deletions Render/SDLShaders/environmentearth.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ struct VSInput
{
// sVertexXYZDT1, stride 24. The diffuse is carried to match the vertex declaration but not
// used -- EnvironmentEarth.psl shades from the texture and the tfactor uniform alone.
float3 Position : POSITION; // offset 0, world space
float4 Color : COLOR0; // offset 12, D3DCOLOR -> UBYTE4_NORM
float2 UV0 : TEXCOORD0; // offset 16
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // offset 0, world space
float4 Color : TEXCOORD1; // offset 12, D3DCOLOR -> UBYTE4_NORM
float2 UV0 : TEXCOORD2; // offset 16
};

struct VSOutput
Expand Down
14 changes: 8 additions & 6 deletions Render/SDLShaders/grass.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,28 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
// shortVertexGrass, stride 28. Attribute locations follow this declaration order.
// shortVertexGrass, stride 28. Attribute locations follow this declaration order, and
// the semantics spell that location out rather than what the data means -- SDL_GPU's
// D3D12 backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
//
// SHORT4, NOT normalized: these are world coordinates in whole units (Bush::pos), which
// is why the position is only 8 bytes. D3D9's D3DDECLTYPE_SHORT4 hands them to the shader
// as floats; SPIR-V hands them over as sint, so we take int4 and convert.
int4 Position : POSITION; // offset 0
int4 Position : TEXCOORD0; // offset 0
// Color4c is stored b,g,r,a in memory, so UBYTE4_NORM gives (b,g,r,a): swizzle to read it.
// rgb is the terrain colour under the bush (vMap.getColor32), a is Bush::shift -- the
// blade's own wind phase, and 0 on the two vertices that stay rooted in the ground.
float4 Color : COLOR0; // offset 8, D3DCOLOR
float4 Color : TEXCOORD1; // offset 8, D3DCOLOR
// The bush's surface normal, biased into 0..1 (hence the *2-1 below). Its ALPHA is not a
// normal component at all: it is a per-blade brightness added to the vertex colour.
float4 Normal : NORMAL; // offset 12, D3DCOLOR
float4 Normal : TEXCOORD2; // offset 12, D3DCOLOR
// xy: the blade's texture coordinate in the grass atlas, pre-multiplied by 10000 so it
// survives as a short (undone by *0.0001 below).
// z: ±half-width of the blade, in world units.
// w: the blade's height, in world units.
int4 TexCoord : TEXCOORD0; // offset 16, SHORT4
int4 TexCoord : TEXCOORD3; // offset 16, SHORT4
// Bush::windPower: the time at which this blade was planted. See `sc` below.
float PlantTime : TEXCOORD1; // offset 24, FLOAT1 (stride 28)
float PlantTime : TEXCOORD4; // offset 24, FLOAT1 (stride 28)
};

struct VSOutput
Expand Down
10 changes: 6 additions & 4 deletions Render/SDLShaders/minimap.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float2 Position : POSITION; // screen pixels
float4 Color : COLOR0; // BGRA (Color4c byte order)
float2 MaskUV : TEXCOORD0;
float2 UV : TEXCOORD1;
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float2 Position : TEXCOORD0; // screen pixels
float4 Color : TEXCOORD1; // BGRA (Color4c byte order)
float2 MaskUV : TEXCOORD2;
float2 UV : TEXCOORD3;
};

struct VSOutput
Expand Down
29 changes: 22 additions & 7 deletions Render/SDLShaders/object3dx.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,35 @@ cbuffer Constants : register(b0, space1)
float4 World[MAX_BONES * 3];
};

// SDL_GPU's D3D12 backend names every vertex input element TEXCOORD<location>, whatever
// the data means, so the semantics below spell out the attribute location rather than the
// D3D9 usage (see SDLShaders/ShaderBlob.h). SKINNED drops one attribute in the middle, so
// everything after it shifts down a location -- exactly as SDLObject3dxRenderer's
// pipelineFor() numbers them, which skips the weight slot for a rigid lod.
#if SKINNED
#define SEM_UV TEXCOORD4
#define SEM_BINORMAL TEXCOORD5
#define SEM_TANGENT TEXCOORD6
#else
#define SEM_UV TEXCOORD3
#define SEM_BINORMAL TEXCOORD4
#define SEM_TANGENT TEXCOORD5
#endif

struct VSInput
{
float3 Position : POSITION; // offset 0
uint4 BlendIndices : BLENDINDICES; // offset 12, D3DCOLOR -> UBYTE4 (memory order)
float3 Normal : NORMAL; // offset 16
float3 Position : TEXCOORD0; // offset 0
uint4 BlendIndices : TEXCOORD1; // offset 12, D3DCOLOR -> UBYTE4 (memory order)
float3 Normal : TEXCOORD2; // offset 16
#if SKINNED
float4 BlendWeight : COLOR0; // offset 28, D3DCOLOR -> UBYTE4_NORM (b,g,r,a)
float4 BlendWeight : TEXCOORD3; // offset 28, D3DCOLOR -> UBYTE4_NORM (b,g,r,a)
#endif
float2 UV : TEXCOORD0; // offset 28 (rigid) / 32 (skinned)
float2 UV : SEM_UV; // offset 28 (rigid) / 32 (skinned)
#if BUMP
// cSkinVertex's tangent frame, right after the uv: BINORMAL is GetBumpS, TANGENT is
// GetBumpT, and cStatic3dx::CalcBumpSTNorm makes the normal their cross product.
float3 Binormal : BINORMAL; // uv + 8
float3 Tangent : TANGENT; // uv + 20
float3 Binormal : SEM_BINORMAL; // uv + 8
float3 Tangent : SEM_TANGENT; // uv + 20
#endif
};

Expand Down
21 changes: 16 additions & 5 deletions Render/SDLShaders/object3dx_shadow.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,26 @@ cbuffer Constants : register(b0, space1)
float4 World[MAX_BONES * 3];
};

// SDL_GPU's D3D12 backend names every vertex input element TEXCOORD<location>, whatever
// the data means, so the semantics below spell out the attribute location rather than the
// D3D9 usage (see SDLShaders/ShaderBlob.h). SKINNED drops one attribute in the middle, so
// the uv that follows it shifts down a location -- exactly as SDLObject3dxRenderer's
// pipelineFor() numbers them, which skips the weight slot for a rigid lod.
#if SKINNED
#define SEM_UV TEXCOORD4
#else
#define SEM_UV TEXCOORD3
#endif

struct VSInput
{
float3 Position : POSITION;
uint4 BlendIndices : BLENDINDICES;
float3 Normal : NORMAL;
float3 Position : TEXCOORD0;
uint4 BlendIndices : TEXCOORD1;
float3 Normal : TEXCOORD2;
#if SKINNED
float4 BlendWeight : COLOR0;
float4 BlendWeight : TEXCOORD3;
#endif
float2 UV : TEXCOORD0;
float2 UV : SEM_UV;
};

struct VSOutput
Expand Down
6 changes: 4 additions & 2 deletions Render/SDLShaders/tilemap.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // world space, offset 0
float3 Normal : NORMAL; // world space, offset 12
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // world space, offset 0
float3 Normal : TEXCOORD1; // world space, offset 12
};

struct VSOutput
Expand Down
6 changes: 4 additions & 2 deletions Render/SDLShaders/tilemap_ice.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // world space, offset 0
float3 Normal : NORMAL; // offset 12 (unused here; kept for the shared mesh layout)
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // world space, offset 0
float3 Normal : TEXCOORD1; // offset 12 (unused here; kept for the shared mesh layout)
};

struct VSOutput
Expand Down
6 changes: 4 additions & 2 deletions Render/SDLShaders/tilemap_lava.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // world space, offset 0
float3 Normal : NORMAL; // world space, offset 12 (unused here; kept for the shared layout)
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // world space, offset 0
float3 Normal : TEXCOORD1; // world space, offset 12 (unused here; kept for the shared layout)
};

struct VSOutput
Expand Down
4 changes: 3 additions & 1 deletion Render/SDLShaders/tilemap_shadow.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ cbuffer Constants : register(b0, space1)
row_major float4x4 MVP; // the light camera's matViewProj
};

float4 main(float3 position : POSITION) : SV_Position
// TEXCOORD0, not POSITION: SDL_GPU's D3D12 backend names every vertex input element
// TEXCOORD<location>, whatever the data means. See SDLShaders/ShaderBlob.h.
float4 main(float3 position : TEXCOORD0) : SV_Position
{
return mul(float4(position, 1.0f), MVP);
}
8 changes: 5 additions & 3 deletions Render/SDLShaders/ui.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ cbuffer Constants : register(b0, space1)
float2 _pad;
};

// Vertex inputs are TEXCOORD<location>, not the semantic the data means -- SDL_GPU's
// D3D12 backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
struct VSInput
{
float4 Position : POSITION; // x,y in pixels
float4 Color : COLOR0; // BGRA (Color4c byte order)
float2 UV : TEXCOORD0;
float4 Position : TEXCOORD0; // x,y in pixels
float4 Color : TEXCOORD1; // BGRA (Color4c byte order)
float2 UV : TEXCOORD2;
};

struct VSOutput
Expand Down
6 changes: 4 additions & 2 deletions Render/SDLShaders/water.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // world space, offset 0
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // world space, offset 0
// sVertexXYZD's D3DCOLOR diffuse, offset 12. Only the alpha is read (the fragment
// shader's per-vertex water opacity, baked by cWater::CalcColor from the depth
// gradient); .w is the alpha byte whichever way the other three are ordered.
float4 Diffuse : COLOR0;
float4 Diffuse : TEXCOORD1;
};

struct VSOutput
Expand Down
6 changes: 4 additions & 2 deletions Render/SDLShaders/water_ice.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // world space, offset 0
float4 Diffuse : COLOR0; // UBYTE4_NORM @12; .r is the coverage weight aa
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // world space, offset 0
float4 Diffuse : TEXCOORD1; // UBYTE4_NORM @12; .r is the coverage weight aa
};

struct VSOutput
Expand Down
8 changes: 5 additions & 3 deletions Render/SDLShaders/worldquad.vert.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ cbuffer Constants : register(b0, space1)

struct VSInput
{
float3 Position : POSITION; // sVertexXYZDT1: world-space position, offset 0
float4 Color : COLOR0; // D3DCOLOR diffuse, offset 12 -> UBYTE4_NORM (b,g,r,a)
float2 UV : TEXCOORD0; // offset 16 (stride 24)
// The semantics are TEXCOORD<location>, not what the data means -- SDL_GPU's D3D12
// backend names every input element TEXCOORD; see SDLShaders/ShaderBlob.h.
float3 Position : TEXCOORD0; // sVertexXYZDT1: world-space position, offset 0
float4 Color : TEXCOORD1; // D3DCOLOR diffuse, offset 12 -> UBYTE4_NORM (b,g,r,a)
float2 UV : TEXCOORD2; // offset 16 (stride 24)
};

struct VSOutput
Expand Down
Loading
Loading