Skip to content

Commit

Permalink
SPIR-V non-float vertex patching (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
thatcosmonaut authored Aug 27, 2024
1 parent bbb9c43 commit 5b2da1d
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 83 deletions.
14 changes: 11 additions & 3 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
226 changes: 146 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,95 @@ 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 (result == NULL)
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 (program == NULL)
{
out_of_memory();
return NULL;
} // if

// We have to patch the SPIR-V output to ensure type consistency. The non-float types 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];
uint32 typeDecl, typeLoad;
SpvOp opcodeLoad;

if (element->vertexElementFormat >= 5 && element->vertexElementFormat <= 7)
{
typeDecl = element->vertexElementFormat == 5 ? vTable->tid_uvec4_p : vTable->tid_ivec4_p;
typeLoad = element->vertexElementFormat == 5 ? vTable->tid_uvec4 : vTable->tid_ivec4;
opcodeLoad = element->vertexElementFormat == 5 ? SpvOpConvertUToF : SpvOpConvertSToF;
}
else
{
typeDecl = vTable->tid_vec4_p;
typeLoad = vTable->tid_vec4;
opcodeLoad = SpvOpCopyObject;
}

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;
}
}

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

SDL_zero(createInfo);
Expand All @@ -377,16 +478,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 +498,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 +560,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 +608,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 +620,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

0 comments on commit 5b2da1d

Please sign in to comment.