mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
c: Add lessons 1-10
This commit is contained in:
392
src/c/lesson10.c
Normal file
392
src/c/lesson10.c
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||
* SPDX-License-Identifier: Zlib
|
||||
*/
|
||||
|
||||
#include "nehe.h"
|
||||
#include <float.h>
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float x, y, z;
|
||||
float u, v;
|
||||
} Vertex;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Vertex vertices[3];
|
||||
} Triangle;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int numTriangles;
|
||||
Triangle* tris;
|
||||
} Sector;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float x, z;
|
||||
float yaw, pitch;
|
||||
float walkBob, walkBobTheta;
|
||||
} Camera;
|
||||
|
||||
|
||||
static SDL_GPUGraphicsPipeline* pso = NULL, * psoBlend = NULL;
|
||||
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||
static SDL_GPUTexture* texture = NULL;
|
||||
static SDL_GPUSampler* samplers[3] = { NULL, NULL, NULL };
|
||||
|
||||
static bool blend = false;
|
||||
static unsigned filter = 0;
|
||||
|
||||
static float projection[16];
|
||||
static Camera camera =
|
||||
{
|
||||
.x = 0.0f, .z = 0.0f,
|
||||
.yaw = 0.0f, .pitch = 0.0f,
|
||||
.walkBob = 0.0f, .walkBobTheta = 0.0f
|
||||
};
|
||||
static Sector world = { .numTriangles = 0, .tris = NULL };
|
||||
|
||||
|
||||
static void ReadLine(SDL_IOStream* restrict file, char* restrict str, int max)
|
||||
{
|
||||
do
|
||||
{
|
||||
char* p = str;
|
||||
for (int n = max - 1; n > 0; --n)
|
||||
{
|
||||
int8_t c;
|
||||
if (!SDL_ReadS8(file, &c))
|
||||
break;
|
||||
(*p++) = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
}
|
||||
(*p) = '\0';
|
||||
} while (str[0] == '/' || str[0] == '\n' || str[0] == '\r');
|
||||
}
|
||||
|
||||
static void SetupWorld(const NeHeContext* ctx)
|
||||
{
|
||||
SDL_IOStream* file = NeHe_OpenResource(ctx, "Data/World.txt", "r");
|
||||
if (!file)
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open \"%s\": %s", "Data/World.txt", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
int numTris;
|
||||
char line[255];
|
||||
ReadLine(file, line, sizeof(line));
|
||||
SDL_sscanf(line, "NUMPOLLIES %d\n", &numTris);
|
||||
|
||||
world.tris = SDL_malloc(sizeof(Triangle) * (size_t)numTris);
|
||||
world.numTriangles = numTris;
|
||||
for (int tri = 0; tri < numTris; ++tri)
|
||||
{
|
||||
for (int vtx = 0; vtx < 3; ++vtx)
|
||||
{
|
||||
ReadLine(file, line, sizeof(line));
|
||||
SDL_sscanf(line, "%f %f %f %f %f",
|
||||
&world.tris[tri].vertices[vtx].x,
|
||||
&world.tris[tri].vertices[vtx].y,
|
||||
&world.tris[tri].vertices[vtx].z,
|
||||
&world.tris[tri].vertices[vtx].u,
|
||||
&world.tris[tri].vertices[vtx].v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool Lesson10_Init(NeHeContext* ctx)
|
||||
{
|
||||
SetupWorld(ctx);
|
||||
|
||||
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson6",
|
||||
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1 }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||
{
|
||||
{
|
||||
.location = 0,
|
||||
.buffer_slot = 0,
|
||||
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||
.offset = offsetof(Vertex, x)
|
||||
},
|
||||
{
|
||||
.location = 1,
|
||||
.buffer_slot = 0,
|
||||
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||
.offset = offsetof(Vertex, u)
|
||||
}
|
||||
};
|
||||
SDL_GPUGraphicsPipelineCreateInfo info =
|
||||
{
|
||||
.vertex_shader = vertexShader,
|
||||
.fragment_shader = fragmentShader,
|
||||
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||
.vertex_input_state =
|
||||
{
|
||||
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||
{
|
||||
.slot = 0,
|
||||
.pitch = sizeof(Vertex),
|
||||
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||
},
|
||||
.num_vertex_buffers = 1,
|
||||
.vertex_attributes = vertexAttribs,
|
||||
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||
},
|
||||
.rasterizer_state =
|
||||
{
|
||||
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE // Right-handed coordinates
|
||||
},
|
||||
.target_info =
|
||||
{
|
||||
.num_color_targets = 1
|
||||
}
|
||||
};
|
||||
|
||||
// Setup blend pipeline
|
||||
const SDL_GPUTextureFormat swapchainTextureFormat = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window);
|
||||
info.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||
{
|
||||
.format = swapchainTextureFormat,
|
||||
.blend_state =
|
||||
{
|
||||
.enable_blend = true,
|
||||
.color_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
|
||||
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE
|
||||
}
|
||||
};
|
||||
psoBlend = SDL_CreateGPUGraphicsPipeline(ctx->device, &info);
|
||||
if (!psoBlend)
|
||||
{
|
||||
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup regular pipeline w/ depth testing
|
||||
info.depth_stencil_state = (SDL_GPUDepthStencilState)
|
||||
{
|
||||
.compare_op = SDL_GPU_COMPAREOP_LESS, // Pass if pixel depth value tests less than the depth buffer value
|
||||
.enable_depth_test = true, // Enable depth testing
|
||||
.enable_depth_write = true
|
||||
};
|
||||
info.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||
{
|
||||
.format = swapchainTextureFormat
|
||||
};
|
||||
info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||
info.target_info.has_depth_stencil_target = true;
|
||||
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &info);
|
||||
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||
if (!pso)
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((texture = NeHe_LoadTexture(ctx, "Data/Mud.bmp", true, true)) == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
samplers[0] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||
{
|
||||
.min_filter = SDL_GPU_FILTER_NEAREST,
|
||||
.mag_filter = SDL_GPU_FILTER_NEAREST
|
||||
});
|
||||
samplers[1] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||
{
|
||||
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||
.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||
});
|
||||
samplers[2] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||
{
|
||||
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||
.mag_filter = SDL_GPU_FILTER_LINEAR,
|
||||
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
|
||||
.max_lod = FLT_MAX
|
||||
});
|
||||
if (!samplers[0] || !samplers[1] || !samplers[2])
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((vtxBuffer = NeHe_CreateVertexBuffer(ctx, world.tris, sizeof(Triangle) * (uint32_t)world.numTriangles)) == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void Lesson10_Quit(NeHeContext* ctx)
|
||||
{
|
||||
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||
for (int i = SDL_arraysize(samplers) - 1; i > 0; --i)
|
||||
{
|
||||
SDL_ReleaseGPUSampler(ctx->device, samplers[i]);
|
||||
}
|
||||
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoBlend);
|
||||
SDL_free(world.tris);
|
||||
}
|
||||
|
||||
static void Lesson10_Resize(NeHeContext* ctx, int width, int height)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
// Avoid division by zero by clamping height
|
||||
height = SDL_max(height, 1);
|
||||
// Recalculate projection matrix
|
||||
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||
}
|
||||
|
||||
static void Lesson10_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||
{
|
||||
const SDL_GPUColorTargetInfo colorInfo =
|
||||
{
|
||||
.texture = swapchain,
|
||||
.clear_color = { 0.0f, 0.0f, 0.0f, 0.0f },
|
||||
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||
.store_op = SDL_GPU_STOREOP_STORE
|
||||
};
|
||||
|
||||
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||
{
|
||||
.texture = ctx->depthTexture,
|
||||
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||
.cycle = true
|
||||
};
|
||||
|
||||
// Begin pass & bind pipeline state
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||
SDL_BindGPUGraphicsPipeline(pass, blend ? psoBlend : pso);
|
||||
|
||||
// Bind texture
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &(SDL_GPUTextureSamplerBinding)
|
||||
{
|
||||
.texture = texture,
|
||||
.sampler = samplers[filter]
|
||||
}, 1);
|
||||
|
||||
// Bind world vertex buffer
|
||||
SDL_BindGPUVertexBuffers(pass, 0, &(SDL_GPUBufferBinding)
|
||||
{
|
||||
.buffer = vtxBuffer,
|
||||
.offset = 0
|
||||
}, 1);
|
||||
|
||||
// Setup the camera view matrix
|
||||
float modelView[16];
|
||||
Mtx_Rotation(modelView, camera.pitch, 1.0f, 0.0f, 0.0f);
|
||||
Mtx_Rotate(modelView, 360.0f - camera.yaw, 0.0f, 1.0f, 0.0f);
|
||||
Mtx_Translate(modelView, -camera.x, -(0.25f + camera.walkBob), -camera.z);
|
||||
|
||||
// Push shader uniforms
|
||||
struct { float modelViewProj[16], color[4]; } u;
|
||||
Mtx_Multiply(u.modelViewProj, projection, modelView);
|
||||
SDL_memcpy(u.color, (float[4]){ 1.0f, 1.0f, 1.0f, 1.0f }, sizeof(float) * 4);
|
||||
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||
|
||||
// Draw world
|
||||
SDL_DrawGPUPrimitives(pass, 3u * (uint32_t)world.numTriangles, 1, 0, 0);
|
||||
|
||||
SDL_EndGPURenderPass(pass);
|
||||
|
||||
// Handle keyboard input
|
||||
const bool *keys = SDL_GetKeyboardState(NULL);
|
||||
|
||||
const float piover180 = 0.0174532925f;
|
||||
|
||||
if (keys[SDL_SCANCODE_UP])
|
||||
{
|
||||
camera.x -= SDL_sinf(camera.yaw * piover180) * 0.05f;
|
||||
camera.z -= SDL_cosf(camera.yaw * piover180) * 0.05f;
|
||||
if (camera.walkBobTheta >= 359.0f)
|
||||
{
|
||||
camera.walkBobTheta = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.walkBobTheta += 10.0f;
|
||||
}
|
||||
camera.walkBob = SDL_sinf(camera.walkBobTheta * piover180) / 20.0f;
|
||||
}
|
||||
|
||||
if (keys[SDL_SCANCODE_DOWN])
|
||||
{
|
||||
camera.x += SDL_sinf(camera.yaw * piover180) * 0.05f;
|
||||
camera.z += SDL_cosf(camera.yaw * piover180) * 0.05f;
|
||||
if (camera.walkBobTheta <= 1.0f)
|
||||
{
|
||||
camera.walkBobTheta = 359.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.walkBobTheta -= 10.0f;
|
||||
}
|
||||
camera.walkBob = SDL_sinf(camera.walkBobTheta * piover180) / 20.0f;
|
||||
}
|
||||
|
||||
if (keys[SDL_SCANCODE_LEFT]) { camera.yaw += 1.0f; }
|
||||
if (keys[SDL_SCANCODE_RIGHT]) { camera.yaw -= 1.0f; }
|
||||
if (keys[SDL_SCANCODE_PAGEUP]) { camera.pitch -= 1.0f; }
|
||||
if (keys[SDL_SCANCODE_PAGEDOWN]) { camera.pitch += 1.0f; }
|
||||
}
|
||||
|
||||
static void Lesson10_Key(NeHeContext* ctx, SDL_Keycode key, bool down, bool repeat)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
if (down && !repeat)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case SDLK_B:
|
||||
blend = !blend;
|
||||
break;
|
||||
case SDLK_F:
|
||||
filter = (filter + 1) % SDL_arraysize(samplers);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const struct AppConfig appConfig =
|
||||
{
|
||||
.title = "Lionel Brits & NeHe's 3D World Tutorial",
|
||||
.width = 640, .height = 480,
|
||||
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||
.init = Lesson10_Init,
|
||||
.quit = Lesson10_Quit,
|
||||
.resize = Lesson10_Resize,
|
||||
.draw = Lesson10_Draw,
|
||||
.key = Lesson10_Key
|
||||
};
|
||||
Reference in New Issue
Block a user