Here’s my version of SDL/test/testgpu_spinning_cube.c file (some code was direct copy-pasted), I went ahead and removed MSAA to reduce lines. It stands at 519 lines right now. I wanted to post this version before I started to break the gpu and object into separate classes.
Please note that I copied the file at SDL/test/testgpu/cube_spirv.h into the current project folder, and linked to it with this compile command
g++ main.cpp -I. -lSDL3
/* A slightly smaller rewrite of SDL/test/testgpu_spinning_cube.c */
#ifndef SIMPLIFIEDSDLGPUCODE
#define SIMPLIFIEDSDLGPUCODE
#include <SDL3/SDL.h>
#include <cube_spirv.h>
typedef struct VertexData
{
float x, y, z, red, green, blue;
} VertexData;
static const VertexData vertexData[] = {
/* Front face. */
/* Bottom left */
{ -0.5, 0.5, -0.5, 1.0, 0.0, 0.0 }, /* red */
{ 0.5, -0.5, -0.5, 0.0, 0.0, 1.0 }, /* blue */
{ -0.5, -0.5, -0.5, 0.0, 1.0, 0.0 }, /* green */
/* Top right */
{ -0.5, 0.5, -0.5, 1.0, 0.0, 0.0 }, /* red */
{ 0.5, 0.5, -0.5, 1.0, 1.0, 0.0 }, /* yellow */
{ 0.5, -0.5, -0.5, 0.0, 0.0, 1.0 }, /* blue */
/* Left face */
/* Bottom left */
{ -0.5, 0.5, 0.5, 1.0, 1.0, 1.0 }, /* white */
{ -0.5, -0.5, -0.5, 0.0, 1.0, 0.0 }, /* green */
{ -0.5, -0.5, 0.5, 0.0, 1.0, 1.0 }, /* cyan */
/* Top right */
{ -0.5, 0.5, 0.5, 1.0, 1.0, 1.0 }, /* white */
{ -0.5, 0.5, -0.5, 1.0, 0.0, 0.0 }, /* red */
{ -0.5, -0.5, -0.5, 0.0, 1.0, 0.0 }, /* green */
/* Top face */
/* Bottom left */
{ -0.5, 0.5, 0.5, 1.0, 1.0, 1.0 }, /* white */
{ 0.5, 0.5, -0.5, 1.0, 1.0, 0.0 }, /* yellow */
{ -0.5, 0.5, -0.5, 1.0, 0.0, 0.0 }, /* red */
/* Top right */
{ -0.5, 0.5, 0.5, 1.0, 1.0, 1.0 }, /* white */
{ 0.5, 0.5, 0.5, 0.0, 0.0, 0.0 }, /* black */
{ 0.5, 0.5, -0.5, 1.0, 1.0, 0.0 }, /* yellow */
/* Right face */
/* Bottom left */
{ 0.5, 0.5, -0.5, 1.0, 1.0, 0.0 }, /* yellow */
{ 0.5, -0.5, 0.5, 1.0, 0.0, 1.0 }, /* magenta */
{ 0.5, -0.5, -0.5, 0.0, 0.0, 1.0 }, /* blue */
/* Top right */
{ 0.5, 0.5, -0.5, 1.0, 1.0, 0.0 }, /* yellow */
{ 0.5, 0.5, 0.5, 0.0, 0.0, 0.0 }, /* black */
{ 0.5, -0.5, 0.5, 1.0, 0.0, 1.0 }, /* magenta */
/* Back face */
/* Bottom left */
{ 0.5, 0.5, 0.5, 0.0, 0.0, 0.0 }, /* black */
{ -0.5, -0.5, 0.5, 0.0, 1.0, 1.0 }, /* cyan */
{ 0.5, -0.5, 0.5, 1.0, 0.0, 1.0 }, /* magenta */
/* Top right */
{ 0.5, 0.5, 0.5, 0.0, 0.0, 0.0 }, /* black */
{ -0.5, 0.5, 0.5, 1.0, 1.0, 1.0 }, /* white */
{ -0.5, -0.5, 0.5, 0.0, 1.0, 1.0 }, /* cyan */
/* Bottom face */
/* Bottom left */
{ -0.5, -0.5, -0.5, 0.0, 1.0, 0.0 }, /* green */
{ 0.5, -0.5, 0.5, 1.0, 0.0, 1.0 }, /* magenta */
{ -0.5, -0.5, 0.5, 0.0, 1.0, 1.0 }, /* cyan */
/* Top right */
{ -0.5, -0.5, -0.5, 0.0, 1.0, 0.0 }, /* green */
{ 0.5, -0.5, -0.5, 0.0, 0.0, 1.0 }, /* blue */
{ 0.5, -0.5, 0.5, 1.0, 0.0, 1.0 } /* magenta */
};
class GPU
{
public:
GPU()
{
initialized = false;
win = NULL;
depthTexture = NULL;
pipeline = NULL;
sampleCount = SDL_GPU_SAMPLECOUNT_1;
}
/*
* Simulates desktop's glRotatef. The matrix is returned in column-major
* order.
*/
void rotate_matrix(float angle, float x, float y, float z, float *r)
{
float radians, c, s, c1, u[3], length;
int i, j;
radians = angle * SDL_PI_F / 180.0f;
c = SDL_cosf(radians);
s = SDL_sinf(radians);
c1 = 1.0f - SDL_cosf(radians);
length = (float)SDL_sqrt(x * x + y * y + z * z);
u[0] = x / length;
u[1] = y / length;
u[2] = z / length;
for (i = 0; i < 16; i++) {
r[i] = 0.0;
}
r[15] = 1.0;
for (i = 0; i < 3; i++) {
r[i * 4 + (i + 1) % 3] = u[(i + 2) % 3] * s;
r[i * 4 + (i + 2) % 3] = -u[(i + 1) % 3] * s;
}
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
r[i * 4 + j] += c1 * u[i] * u[j] + (i == j ? c : 0.0f);
}
}
}
/*
* Simulates gluPerspectiveMatrix
*/
void perspective_matrix(float fovy, float aspect, float znear, float zfar, float *r)
{
int i;
float f;
f = 1.0f/SDL_tanf(fovy * 0.5f);
for (i = 0; i < 16; i++) {
r[i] = 0.0;
}
r[0] = f / aspect;
r[5] = f;
r[10] = (znear + zfar) / (znear - zfar);
r[11] = -1.0f;
r[14] = (2.0f * znear * zfar) / (znear - zfar);
r[15] = 0.0f;
}
/*
* Multiplies lhs by rhs and writes out to r. All matrices are 4x4 and column
* major. In-place multiplication is supported.
*/
void multiply_matrix(float *lhs, float *rhs, float *r)
{
int i, j, k;
float tmp[16];
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
tmp[j * 4 + i] = 0.0;
for (k = 0; k < 4; k++) {
tmp[j * 4 + i] += lhs[k * 4 + i] * rhs[j * 4 + k];
}
}
}
for (i = 0; i < 16; i++) {
r[i] = tmp[i];
}
}
SDL_GPUTexture * CreateDepthTexture()
{
SDL_GPUTextureCreateInfo depthTextureInfo;
SDL_GPUTexture * result;
SDL_zero(depthTextureInfo);
depthTextureInfo.type = SDL_GPU_TEXTURETYPE_2D;
depthTextureInfo.format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
depthTextureInfo.width = windowW;
depthTextureInfo.height = windowH;
depthTextureInfo.layer_count_or_depth = 1;
depthTextureInfo.num_levels = 1;
depthTextureInfo.sample_count = sampleCount;
depthTextureInfo.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
depthTextureInfo.props = 0;
result = SDL_CreateGPUTexture(gpuDevice, &depthTextureInfo);
if(!result)
{
SDL_Log("Failed to create depth Texture: %s", SDL_GetError());
}
return result;
}
bool init(SDL_Window * targetWindow, const char * gpuName = NULL)
{
win = targetWindow;
SDL_GetWindowSize(win, &windowW, &windowH);
oldWindowW = windowW;
oldWindowH = windowH;
if(initialized)
{
// unclaim old window, glose GPU
}
// Claim window
SDL_GPUShaderFormat spirv = SDL_GPU_SHADERFORMAT_SPIRV;
gpuDevice = SDL_CreateGPUDevice(spirv, SDL_TRUE, NULL);
SDL_ClaimWindowForGPUDevice(gpuDevice, win);
initialized = true;
// Prep Shaders
// Vertex Shader
SDL_GPUShaderCreateInfo shaderInfo;
SDL_zero(shaderInfo);
shaderInfo.num_samplers = 0;
shaderInfo.num_storage_buffers = 0;
shaderInfo.num_storage_textures = 0;
shaderInfo.num_uniform_buffers = 1;
shaderInfo.props = 0;
shaderInfo.format = SDL_GPU_SHADERFORMAT_SPIRV;
shaderInfo.code = cube_vert_spv;
shaderInfo.code_size = cube_vert_spv_len;
shaderInfo.entrypoint = "main";
shaderInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX;
SDL_GPUShader * vertexShader = SDL_CreateGPUShader(gpuDevice, &shaderInfo);
// Fragment Shader
shaderInfo.num_uniform_buffers = 0;
shaderInfo.code = cube_frag_spv;
shaderInfo.code_size = cube_frag_spv_len;
shaderInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
SDL_GPUShader * fragmentShader = SDL_CreateGPUShader(gpuDevice, &shaderInfo);
if(!(fragmentShader && vertexShader))
{
SDL_Log("A shader has failed.");
initialized = false;
SDL_Log("Error check: %s", SDL_GetError());
return false;
}
// Create Buffers
SDL_GPUBufferCreateInfo bufferInfo;
bufferInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
bufferInfo.size = sizeof(vertexData);
bufferInfo.props = 0;
activeBuffer = SDL_CreateGPUBuffer(gpuDevice, &bufferInfo);
SDL_SetGPUBufferName(gpuDevice, activeBuffer, "MyGPUBuffer");
// Transfer Buffer
SDL_GPUTransferBufferCreateInfo tbInfo;
tbInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tbInfo.size = sizeof(vertexData);
tbInfo.props = 0;
transferBuffer = SDL_CreateGPUTransferBuffer(gpuDevice, &tbInfo);
if(!transferBuffer)
{
initialized = false;
SDL_Log("Error check: %s", SDL_GetError());
return false;
}
// Upload Static Data
void * map = SDL_MapGPUTransferBuffer(gpuDevice, transferBuffer, SDL_FALSE);
SDL_memcpy(map, vertexData, sizeof(vertexData));
SDL_UnmapGPUTransferBuffer(gpuDevice, transferBuffer);
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(gpuDevice);
SDL_GPUCopyPass *cpass = SDL_BeginGPUCopyPass(cmd);
SDL_GPUTransferBufferLocation loc;
loc.transfer_buffer = transferBuffer;
loc.offset = 0;
SDL_GPUBufferRegion dest;
dest.buffer = activeBuffer;
dest.offset = 0;
dest.size = sizeof(vertexData);
SDL_UploadToGPUBuffer(cpass, &loc, &dest, SDL_FALSE);
SDL_EndGPUCopyPass(cpass);
SDL_SubmitGPUCommandBuffer(cmd);
SDL_ReleaseGPUTransferBuffer(gpuDevice, transferBuffer);
sampleCount = SDL_GPU_SAMPLECOUNT_1;
// Graphics Pipeline setup
SDL_GPUColorTargetDescription tempColor;
SDL_GPUGraphicsPipelineCreateInfo pipelineInfo;
SDL_zero(pipelineInfo);
SDL_zero(tempColor);
tempColor.format = SDL_GetGPUSwapchainTextureFormat(gpuDevice, win);
pipelineInfo.target_info.num_color_targets = 1;
pipelineInfo.target_info.color_target_descriptions = &tempColor;
pipelineInfo.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
pipelineInfo.target_info.has_depth_stencil_target = SDL_TRUE;
pipelineInfo.depth_stencil_state.enable_depth_test = 1;
pipelineInfo.depth_stencil_state.enable_depth_write = 1;
pipelineInfo.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
pipelineInfo.multisample_state.sample_count = sampleCount;
pipelineInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
pipelineInfo.vertex_shader = vertexShader;
pipelineInfo.fragment_shader = fragmentShader;
vertexBinding.index = 0;
vertexBinding.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
vertexBinding.instance_step_rate = 0;
vertexBinding.pitch = sizeof(VertexData);
SDL_GPUVertexAttribute vertexAttributes[2];
vertexAttributes[0].binding_index = 0;
vertexAttributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
vertexAttributes[0].location = 0;
vertexAttributes[0].offset = 0;
vertexAttributes[1].binding_index = 0;
vertexAttributes[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
vertexAttributes[1].location = 1;
vertexAttributes[1].offset = sizeof(float) * 3;
pipelineInfo.vertex_input_state.num_vertex_bindings = 1;
pipelineInfo.vertex_input_state.vertex_bindings = &vertexBinding;
pipelineInfo.vertex_input_state.num_vertex_attributes = 2;
pipelineInfo.vertex_input_state.vertex_attributes = (SDL_GPUVertexAttribute*) &vertexAttributes;
pipelineInfo.props = 0;
pipeline = SDL_CreateGPUGraphicsPipeline(gpuDevice, &pipelineInfo);
if(!pipeline)
{
SDL_Log("Error creating pipeline: %s", SDL_GetError());
return false;
}
SDL_ReleaseGPUShader(gpuDevice, vertexShader);
SDL_ReleaseGPUShader(gpuDevice, fragmentShader);
SDL_GetWindowSizeInPixels(win, &windowW, &windowH);
// depthTexture info
depthTexture = CreateDepthTexture();
angleX = 10;
angleY = 20;
angleZ = 30;
return true;
}
void close()
{
SDL_ReleaseGPUTexture(gpuDevice, depthTexture);
SDL_ReleaseWindowFromGPUDevice(gpuDevice, win);
SDL_ReleaseGPUBuffer(gpuDevice, activeBuffer);
SDL_ReleaseGPUGraphicsPipeline(gpuDevice, pipeline);
SDL_DestroyGPUDevice(gpuDevice);
gpuDevice = NULL;
}
void resize()
{
if(depthTexture)
{
SDL_ReleaseGPUTexture(gpuDevice, depthTexture);
}
SDL_GetWindowSize(win, &windowW, &windowH);
depthTexture = CreateDepthTexture();
}
void draw()
{
SDL_GPUCommandBuffer * cmdBuffer;
cmdBuffer = SDL_AcquireGPUCommandBuffer(gpuDevice);
if(cmdBuffer)
{
swapchainTexture = SDL_AcquireGPUSwapchainTexture(cmdBuffer, win, (unsigned int *) &windowW, (unsigned int *) &windowH);
if(swapchainTexture)
{
// double check window is correct size
if(oldWindowW != windowW || oldWindowH != windowH)
{
resize();
}
oldWindowW = windowW;
oldWindowH = windowH;
float matrix_rotate[16], matrix_modelview[16], matrix_perspective[16], matrix_final[16];
rotate_matrix((float)angleX, 1.0f, 0.0f, 0.0f, matrix_modelview);
rotate_matrix((float)angleZ, 0.0f, 1.0f, 0.0f, matrix_rotate);
multiply_matrix(matrix_rotate, matrix_modelview, matrix_modelview);
/* Pull the camera back from the cube */
matrix_modelview[14] -= 2.5f;
perspective_matrix(45.0f, (float)windowW/windowH, 0.01f, 100.0f, matrix_perspective);
multiply_matrix(matrix_perspective, matrix_modelview, (float*) &matrix_final);
angleX += 3;
angleY += 2;
angleZ += 1;
if(angleX >= 360) angleX -= 360;
if(angleX < 0) angleX += 360;
if(angleY >= 360) angleY -= 360;
if(angleY < 0) angleY += 360;
if(angleZ >= 360) angleZ -= 360;
if(angleZ < 0) angleZ += 360;
SDL_zero(colorInfo);
colorInfo.clear_color.a = 1.0f;
colorInfo.clear_color.r = 0.5f;
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR;
colorInfo.store_op = SDL_GPU_STOREOP_STORE;
colorInfo.texture = swapchainTexture;
SDL_GPUDepthStencilTargetInfo depthInfo;
SDL_zero(depthInfo);
depthInfo.clear_depth = 1.0f;
depthInfo.load_op = SDL_GPU_LOADOP_CLEAR;
depthInfo.store_op = SDL_GPU_STOREOP_DONT_CARE;
depthInfo.texture = depthTexture;
depthInfo.cycle = SDL_TRUE;
SDL_GPUBufferBinding binding;
binding.buffer = activeBuffer;
binding.offset = 0;
SDL_PushGPUVertexUniformData(cmdBuffer, 0, matrix_final, sizeof(matrix_final));
renderPass = SDL_BeginGPURenderPass(cmdBuffer, &colorInfo, 1, &depthInfo);
SDL_BindGPUGraphicsPipeline(renderPass, pipeline);
SDL_BindGPUVertexBuffers(renderPass, 0, &binding, 1);
SDL_DrawGPUPrimitives(renderPass, 36, 1, 0, 0);
SDL_EndGPURenderPass(renderPass);
frameCount ++;
}
SDL_SubmitGPUCommandBuffer(cmdBuffer);
}
}
public:
SDL_GPUDevice * gpuDevice;
SDL_GPUGraphicsPipeline * pipeline;
SDL_GPUSampleCount sampleCount;
SDL_GPUBuffer * activeBuffer;
SDL_GPUTransferBuffer * transferBuffer;
SDL_GPUComputePipeline * computePipeline;
//Initialization
SDL_GPUVertexBinding vertexBinding;
// Rendering
SDL_GPURenderPass * renderPass;
SDL_GPUColorTargetInfo colorInfo;
// GPU Textures
SDL_GPUTexture * depthTexture;
SDL_GPUTexture * swapchainTexture;
size_t frameCount;
SDL_Window * win;
int windowW, windowH, oldWindowW, oldWindowH;
bool initialized;
float angleX, angleY, angleZ;
};
#endif
int main()
{
SDL_Init(SDL_INIT_VIDEO);
SDL_Window * win = SDL_CreateWindow("My GPU", 800, 800, SDL_WINDOW_RESIZABLE);
GPU myGPU;
myGPU.windowW = 800;
myGPU.windowH = 800;
myGPU.init(win, NULL);
bool run = true;
while(run)
{
SDL_Event ev;
while(SDL_PollEvent(&ev))
{
switch(ev.type)
{
case SDL_EVENT_WINDOW_RESIZED:
break;
case SDL_EVENT_KEY_DOWN:
switch(ev.key.key)
{
case SDLK_ESCAPE:
run = false;
break;
}
break;
case SDL_EVENT_QUIT:
run = false;
break;
}
}
myGPU.draw();
}
myGPU.close();
SDL_DestroyWindow(win);
SDL_Quit();
}
You probably will want MSAA later on for antialiasing. You will see the issue if you slow down the cube to a snail’s pace.