/* * SPDX-FileCopyrightText: (C) 2025 a dinosaur * SPDX-License-Identifier: Zlib */ #include "nehe.h" typedef struct { float x, y, z; float u, v; } Vertex; static const Vertex vertices[] = { // Front Face { -1.0f, -1.0f, 1.0f, 0.0f, 0.0f }, { 1.0f, -1.0f, 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, { -1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, // Back Face { -1.0f, -1.0f, -1.0f, 1.0f, 0.0f }, { -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, -1.0f, 0.0f, 1.0f }, { 1.0f, -1.0f, -1.0f, 0.0f, 0.0f }, // Top Face { -1.0f, 1.0f, -1.0f, 0.0f, 1.0f }, { -1.0f, 1.0f, 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f }, // Bottom Face { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f }, { 1.0f, -1.0f, -1.0f, 0.0f, 1.0f }, { 1.0f, -1.0f, 1.0f, 0.0f, 0.0f }, { -1.0f, -1.0f, 1.0f, 1.0f, 0.0f }, // Right face { 1.0f, -1.0f, -1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, { 1.0f, -1.0f, 1.0f, 0.0f, 0.0f }, // Left Face { -1.0f, -1.0f, -1.0f, 0.0f, 0.0f }, { -1.0f, -1.0f, 1.0f, 1.0f, 0.0f }, { -1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, { -1.0f, 1.0f, -1.0f, 0.0f, 1.0f } }; static const uint16_t indices[] = { 0, 1, 2, 2, 3, 0, // Front 4, 5, 6, 6, 7, 4, // Back 8, 9, 10, 10, 11, 8, // Top 12, 13, 14, 14, 15, 12, // Bottom 16, 17, 18, 18, 19, 16, // Right 20, 21, 22, 22, 23, 20 // Left }; static SDL_GPUGraphicsPipeline* pso = NULL; static SDL_GPUBuffer* vtxBuffer = NULL; static SDL_GPUBuffer* idxBuffer = NULL; static SDL_GPUSampler* sampler = NULL; static SDL_GPUTexture* texture = NULL; static float projection[16]; static float xRot = 0.0f, yRot = 0.0f, zRot = 0.0f; static bool Lesson6_Init(NeHeContext* restrict 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) } }; pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo) { .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 }, .depth_stencil_state = { .compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL, .enable_depth_test = true, .enable_depth_write = true }, .target_info = { .color_target_descriptions = &(const SDL_GPUColorTargetDescription) { .format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window) }, .num_color_targets = 1, .depth_stencil_format = appConfig.createDepthFormat, .has_depth_stencil_target = true } }); 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/NeHe.bmp", true, false)) == NULL) { return false; } sampler = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo) { .min_filter = SDL_GPU_FILTER_LINEAR, .mag_filter = SDL_GPU_FILTER_LINEAR }); if (!sampler) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError()); return false; } if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer, vertices, sizeof(vertices), indices, sizeof(indices))) { return false; } return true; } static void Lesson6_Quit(NeHeContext* restrict ctx) { SDL_ReleaseGPUBuffer(ctx->device, idxBuffer); SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer); SDL_ReleaseGPUSampler(ctx->device, sampler); SDL_ReleaseGPUTexture(ctx->device, texture); SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso); } static void Lesson6_Resize(NeHeContext* restrict 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 Lesson6_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.5f }, .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, pso); // Bind texture SDL_BindGPUFragmentSamplers(pass, 0, &(const SDL_GPUTextureSamplerBinding) { .texture = texture, .sampler = sampler }, 1); // Bind vertex & index buffers SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding) { .buffer = vtxBuffer, .offset = 0 }, 1); SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding) { .buffer = idxBuffer, .offset = 0 }, SDL_GPU_INDEXELEMENTSIZE_16BIT); float model[16]; struct { float modelViewProj[16], color[4]; } u; // Move cube 5 units into the screen and apply some rotations Mtx_Translation(model, 0.0f, 0.0f, -5.0f); Mtx_Rotate(model, xRot, 1.0f, 0.0f, 0.0f); Mtx_Rotate(model, yRot, 0.0f, 1.0f, 0.0f); Mtx_Rotate(model, zRot, 0.0f, 0.0f, 1.0f); // Push shader uniforms Mtx_Multiply(u.modelViewProj, projection, model); 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 textured cube SDL_DrawGPUIndexedPrimitives(pass, SDL_arraysize(indices), 1, 0, 0, 0); SDL_EndGPURenderPass(pass); xRot += 0.3f; yRot += 0.2f; zRot += 0.4f; } const struct AppConfig appConfig = { .title = "NeHe's Texture Mapping Tutorial", .width = 640, .height = 480, .createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM, .init = Lesson6_Init, .quit = Lesson6_Quit, .resize = Lesson6_Resize, .draw = Lesson6_Draw };