Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPIR-V non-float vertex patching #71

Merged
merged 7 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
15 changes: 11 additions & 4 deletions mojoshader.h
Original file line number Diff line number Diff line change
Expand Up @@ -4098,6 +4098,12 @@ DECLSPEC void MOJOSHADER_d3d11DestroyContext(MOJOSHADER_d3d11Context *context);
typedef struct MOJOSHADER_sdlContext MOJOSHADER_sdlContext;
typedef struct MOJOSHADER_sdlShaderData MOJOSHADER_sdlShaderData;
typedef struct MOJOSHADER_sdlProgram MOJOSHADER_sdlProgram;
typedef struct MOJOSHADER_sdlVertexAttribute
{
MOJOSHADER_usage usage;
int vertexElementFormat; /* FNA3D_VertexElementFormat */
int usageIndex;
} MOJOSHADER_sdlVertexAttribute;

#ifndef SDL_GPU_H
typedef struct SDL_GpuDevice SDL_GpuDevice;
Expand Down Expand Up @@ -4223,12 +4229,14 @@ DECLSPEC const MOJOSHADER_parseData *MOJOSHADER_sdlGetShaderParseData(
MOJOSHADER_sdlShaderData *shader);

/*
* Link a vertex and pixel shader into a working SDL_gpu shader program.
* Link bound vertex and pixel shader into a working SDL_gpu shader program.
* (vshader) or (pshader) can NOT be NULL, unlike OpenGL.
*
* You can reuse shaders in various combinations across
* multiple programs, by relinking different pairs.
*
* Requires vertex element data for patches.
*
* It is illegal to give a vertex shader for (pshader) or a pixel shader
* for (vshader).
*
Expand All @@ -4237,8 +4245,8 @@ DECLSPEC const MOJOSHADER_parseData *MOJOSHADER_sdlGetShaderParseData(
* Returns NULL on error, or a program handle on success.
*/
DECLSPEC MOJOSHADER_sdlProgram *MOJOSHADER_sdlLinkProgram(MOJOSHADER_sdlContext *context,
MOJOSHADER_sdlShaderData *vshader,
MOJOSHADER_sdlShaderData *pshader);
MOJOSHADER_sdlVertexAttribute *vertexAttributes,
int vertexAttributeCount);

/*
* This binds the program to the active context, and does nothing particularly
Expand Down Expand Up @@ -4354,4 +4362,3 @@ DECLSPEC unsigned int MOJOSHADER_sdlGetSamplerSlots(MOJOSHADER_sdlShaderData *sh
#endif /* include-once blocker. */

/* end of mojoshader.h ... */

217 changes: 137 additions & 80 deletions mojoshader_sdlgpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,36 @@ static inline void out_of_memory(void)

/* Internals */

typedef struct BoundShaders
typedef struct LinkedShaderData
{
MOJOSHADER_sdlShaderData *vertex;
MOJOSHADER_sdlShaderData *fragment;
} BoundShaders;
MOJOSHADER_sdlVertexAttribute vertexAttributes[16];
uint32_t vertexAttributeCount;
} LinkedShaderData;

static uint32_t hash_shaders(const void *sym, void *data)
{
(void) data;
const BoundShaders *s = (const BoundShaders *) sym;
const uint16_t v = (s->vertex) ? s->vertex->tag : 0;
const uint16_t f = (s->fragment) ? s->fragment->tag : 0;
return ((uint32_t) v << 16) | (uint32_t) f;
const LinkedShaderData *s = (const LinkedShaderData *) sym;
const uint32_t HASH_FACTOR = 31;
uint32_t hash = s->vertexAttributeCount;
for (uint32_t i = 0; i < s->vertexAttributeCount; i += 1)
{
hash = hash * HASH_FACTOR + s->vertexAttributes[i].usage;
hash = hash * HASH_FACTOR + s->vertexAttributes[i].usageIndex;
hash = hash * HASH_FACTOR + s->vertexAttributes[i].vertexElementFormat;
}
hash = hash * HASH_FACTOR + s->vertex->tag;
hash = hash * HASH_FACTOR + s->fragment->tag;
return hash;
} // hash_shaders

static int match_shaders(const void *_a, const void *_b, void *data)
{
(void) data;
const BoundShaders *a = (const BoundShaders *) _a;
const BoundShaders *b = (const BoundShaders *) _b;
const LinkedShaderData *a = (const LinkedShaderData *) _a;
const LinkedShaderData *b = (const LinkedShaderData *) _b;

const uint16_t av = (a->vertex) ? a->vertex->tag : 0;
const uint16_t bv = (b->vertex) ? b->vertex->tag : 0;
Expand All @@ -111,6 +121,25 @@ static int match_shaders(const void *_a, const void *_b, void *data)
if (af != bf)
return 0;

if (a->vertexAttributeCount != b->vertexAttributeCount)
return 0;

for (uint32_t i = 0; i < a->vertexAttributeCount; i += 1)
{
if (a->vertexAttributes[i].usage != b->vertexAttributes[i].usage)
{
return 0;
}
if (a->vertexAttributes[i].usageIndex != b->vertexAttributes[i].usageIndex)
{
return 0;
}
if (a->vertexAttributes[i].vertexElementFormat != b->vertexAttributes[i].vertexElementFormat)
{
return 0;
}
}

return 1;
} // match_shaders

Expand All @@ -122,7 +151,7 @@ static void nuke_shaders(
) {
MOJOSHADER_sdlContext *ctx = (MOJOSHADER_sdlContext *) _ctx;
(void) data;
ctx->free_fn((void *) key, ctx->malloc_data); // this was a BoundShaders struct.
ctx->free_fn((void *) key, ctx->malloc_data); // this was a LinkedShaderData struct.
MOJOSHADER_sdlDeleteProgram(ctx, (MOJOSHADER_sdlProgram *) value);
} // nuke_shaders

Expand Down Expand Up @@ -349,23 +378,86 @@ MOJOSHADER_sdlShaderData *MOJOSHADER_sdlCompileShader(

MOJOSHADER_sdlProgram *MOJOSHADER_sdlLinkProgram(
MOJOSHADER_sdlContext *ctx,
MOJOSHADER_sdlShaderData *vshader,
MOJOSHADER_sdlShaderData *pshader
MOJOSHADER_sdlVertexAttribute *vertexAttributes,
int vertexAttributeCount
) {
MOJOSHADER_sdlProgram *result;
MOJOSHADER_sdlProgram *program = NULL;
SDL_GpuShaderCreateInfo createInfo;

MOJOSHADER_sdlShaderData *vshader = ctx->bound_vshader_data;
MOJOSHADER_sdlShaderData *pshader = ctx->bound_pshader_data;

if ((vshader == NULL) || (pshader == NULL)) /* Both shaders MUST exist! */
return NULL;

result = (MOJOSHADER_sdlProgram*) ctx->malloc_fn(sizeof(MOJOSHADER_sdlProgram), ctx->malloc_data);
if (ctx->linker_cache == NULL)
{
ctx->linker_cache = hash_create(NULL, hash_shaders, match_shaders,
nuke_shaders, 0, ctx->malloc_fn,
ctx->free_fn, ctx->malloc_data);

if (ctx->linker_cache == NULL)
{
out_of_memory();
return NULL;
} // if
} // if

LinkedShaderData shaders;
shaders.vertex = vshader;
shaders.fragment = pshader;
memset(shaders.vertexAttributes, 0, sizeof(MOJOSHADER_sdlVertexAttribute) * 16);
shaders.vertexAttributeCount = vertexAttributeCount;
for (int i = 0; i < vertexAttributeCount; i += 1)
{
shaders.vertexAttributes[i] = vertexAttributes[i];
}

const void *val = NULL;

if (hash_find(ctx->linker_cache, &shaders, &val))
{
ctx->bound_program = (MOJOSHADER_sdlProgram *) val;
return ctx->bound_program;
}

program = (MOJOSHADER_sdlProgram*) ctx->malloc_fn(sizeof(MOJOSHADER_sdlProgram), ctx->malloc_data);

if (result == NULL)
if (program == NULL)
{
out_of_memory();
return NULL;
} // if

// We have to patch the SPIR-V output for non-float inputs. These are:
// BYTE4 - 5
// SHORT2 - 6
// SHORT4 - 7
int vDataLen = vshader->parseData->output_len - sizeof(SpirvPatchTable);
SpirvPatchTable *vTable = (SpirvPatchTable *) &vshader->parseData->output[vDataLen];

for (int i = 0; i < vertexAttributeCount; i += 1)
{
MOJOSHADER_sdlVertexAttribute *element = &vertexAttributes[i];
if (element->vertexElementFormat >= 5 && element->vertexElementFormat <= 7)
{
uint32 typeDecl = element->vertexElementFormat == 5 ? vTable->tid_uvec4_p : vTable->tid_ivec4_p;
uint32 typeLoad = element->vertexElementFormat == 5 ? vTable->tid_uvec4 : vTable->tid_ivec4;
SpvOp opcodeLoad = element->vertexElementFormat == 5 ? SpvOpConvertUToF : SpvOpConvertSToF;

uint32_t typeDeclOffset = vTable->attrib_type_offsets[element->usage][element->usageIndex];
((uint32_t*)vshader->parseData->output)[typeDeclOffset] = typeDecl;
for (uint32_t j = 0; j < vTable->attrib_type_load_offsets[element->usage][element->usageIndex].num_loads; j += 1)
{
uint32_t typeLoadOffset = vTable->attrib_type_load_offsets[element->usage][element->usageIndex].load_types[j];
uint32_t opcodeLoadOffset = vTable->attrib_type_load_offsets[element->usage][element->usageIndex].load_opcodes[j];
uint32_t *ptr_to_opcode_u32 = &((uint32_t*)vshader->parseData->output)[opcodeLoadOffset];
((uint32_t*)vshader->parseData->output)[typeLoadOffset] = typeLoad;
*ptr_to_opcode_u32 = (*ptr_to_opcode_u32 & 0xFFFF0000) | opcodeLoad;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also patch in the opposite direction, in case one layout uses short and another uses float (unlikely but I wouldn't want to debug that without advance knowledge...)

}

MOJOSHADER_spirv_link_attributes(vshader->parseData, pshader->parseData, 0);

SDL_zero(createInfo);
Expand All @@ -377,16 +469,16 @@ MOJOSHADER_sdlProgram *MOJOSHADER_sdlLinkProgram(
createInfo.samplerCount = vshader->samplerSlots;
createInfo.uniformBufferCount = 1;

result->vertexShader = SDL_ShaderCross_CompileFromSPIRV(
program->vertexShader = SDL_ShaderCross_CompileFromSPIRV(
ctx->device,
&createInfo,
SDL_FALSE
);

if (result->vertexShader == NULL)
if (program->vertexShader == NULL)
{
set_error(SDL_GetError());
ctx->free_fn(result, ctx->malloc_data);
ctx->free_fn(program, ctx->malloc_data);
return NULL;
} // if

Expand All @@ -397,24 +489,42 @@ MOJOSHADER_sdlProgram *MOJOSHADER_sdlLinkProgram(
createInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
createInfo.samplerCount = pshader->samplerSlots;

result->pixelShader = SDL_ShaderCross_CompileFromSPIRV(
program->pixelShader = SDL_ShaderCross_CompileFromSPIRV(
ctx->device,
&createInfo,
SDL_FALSE
);

if (result->pixelShader == NULL)
if (program->pixelShader == NULL)
{
set_error(SDL_GetError());
SDL_GpuReleaseShader(ctx->device, result->vertexShader);
ctx->free_fn(result, ctx->malloc_data);
SDL_GpuReleaseShader(ctx->device, program->vertexShader);
ctx->free_fn(program, ctx->malloc_data);
return NULL;
} // if

result->vertexShaderData = vshader;
result->pixelShaderData = pshader;
program->vertexShaderData = vshader;
program->pixelShaderData = pshader;

LinkedShaderData *item = (LinkedShaderData *) ctx->malloc_fn(sizeof (LinkedShaderData),
ctx->malloc_data);

return result;
if (item == NULL)
{
MOJOSHADER_sdlDeleteProgram(ctx, program);
}

memcpy(item, &shaders, sizeof(LinkedShaderData));
if (hash_insert(ctx->linker_cache, item, program) != 1)
{
ctx->free_fn(item, ctx->malloc_data);
MOJOSHADER_sdlDeleteProgram(ctx, program);
out_of_memory();
return NULL;
}

ctx->bound_program = program;
return program;
} // MOJOSHADER_sdlLinkProgram

void MOJOSHADER_sdlShaderAddRef(MOJOSHADER_sdlShaderData *shader)
Expand All @@ -441,7 +551,7 @@ void MOJOSHADER_sdlDeleteShader(
int morekeys = hash_iter_keys(ctx->linker_cache, &key, &iter);
while (morekeys)
{
const BoundShaders *shaders = (const BoundShaders *) key;
const LinkedShaderData *shaders = (const LinkedShaderData *) key;
// Do this here so we don't confuse the iteration by removing...
morekeys = hash_iter_keys(ctx->linker_cache, &key, &iter);
if ((shaders->vertex == shader) || (shaders->fragment == shader))
Expand Down Expand Up @@ -489,56 +599,9 @@ void MOJOSHADER_sdlBindShaders(
MOJOSHADER_sdlShaderData *vshader,
MOJOSHADER_sdlShaderData *pshader
) {
if (ctx->linker_cache == NULL)
{
ctx->linker_cache = hash_create(NULL, hash_shaders, match_shaders,
nuke_shaders, 0, ctx->malloc_fn,
ctx->free_fn, ctx->malloc_data);

if (ctx->linker_cache == NULL)
{
out_of_memory();
return;
} // if
} // if

MOJOSHADER_sdlProgram *program = NULL;
BoundShaders shaders;
shaders.vertex = vshader;
shaders.fragment = pshader;

ctx->bound_vshader_data = vshader;
ctx->bound_pshader_data = pshader;

const void *val = NULL;
if (hash_find(ctx->linker_cache, &shaders, &val))
program = (MOJOSHADER_sdlProgram *) val;
else
{
program = MOJOSHADER_sdlLinkProgram(ctx, vshader, pshader);
if (program == NULL)
return;

BoundShaders *item = (BoundShaders *) ctx->malloc_fn(sizeof (BoundShaders),
ctx->malloc_data);
if (item == NULL)
{
MOJOSHADER_sdlDeleteProgram(ctx, program);
return;
} // if

memcpy(item, &shaders, sizeof (BoundShaders));
if (hash_insert(ctx->linker_cache, item, program) != 1)
{
ctx->free_fn(item, ctx->malloc_data);
MOJOSHADER_sdlDeleteProgram(ctx, program);
out_of_memory();
return;
} // if
} // else

SDL_assert(program != NULL);
ctx->bound_program = program;
} // MOJOSHADER_sdlBindShaders

void MOJOSHADER_sdlGetBoundShaderData(
Expand All @@ -548,17 +611,11 @@ void MOJOSHADER_sdlGetBoundShaderData(
) {
if (vshaderdata != NULL)
{
if (ctx->bound_program != NULL)
*vshaderdata = ctx->bound_program->vertexShaderData;
else
*vshaderdata = ctx->bound_vshader_data; // In case a pshader isn't set yet
*vshaderdata = ctx->bound_vshader_data;
} // if
if (pshaderdata != NULL)
{
if (ctx->bound_program != NULL)
*pshaderdata = ctx->bound_program->pixelShaderData;
else
*pshaderdata = ctx->bound_pshader_data; // In case a vshader isn't set yet
*pshaderdata = ctx->bound_pshader_data;
} // if
} // MOJOSHADER_sdlGetBoundShaderData

Expand Down
Loading