From 5f8a4656fe1e338916c19faa82b46ef460f919c5 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 15 Sep 2024 14:46:16 +0200 Subject: [PATCH] Implemented rotating cube. --- assets/bitmaps/cube.png | Bin 0 -> 9876 bytes assets/meshes/cube.mtl | 12 ++ .../textured_3dtriangles_from_buffer.vert | 23 +++ external/scons-plus-plus | 2 +- private/sdl_gpu_test/2_textured_cube/app.cpp | 176 ++++++++++++++++++ private/sdl_gpu_test/2_textured_cube/app.hpp | 28 +++ private/sdl_gpu_test/SModule | 5 +- private/sdl_gpu_test/application.cpp | 11 +- private/sdl_gpu_test/application.hpp | 2 +- private/sdl_gpu_test/main.cpp | 4 +- private/sdl_gpu_test/sdlpp/gpu.hpp | 14 +- private/sdl_gpu_test/util/mesh.cpp | 75 ++++++++ private/sdl_gpu_test/util/mesh.hpp | 34 ++++ 13 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 assets/bitmaps/cube.png create mode 100644 assets/meshes/cube.mtl create mode 100644 assets/shaders/glsl/textured_3dtriangles_from_buffer.vert create mode 100644 private/sdl_gpu_test/2_textured_cube/app.cpp create mode 100644 private/sdl_gpu_test/2_textured_cube/app.hpp create mode 100644 private/sdl_gpu_test/util/mesh.cpp create mode 100644 private/sdl_gpu_test/util/mesh.hpp diff --git a/assets/bitmaps/cube.png b/assets/bitmaps/cube.png new file mode 100644 index 0000000000000000000000000000000000000000..9219aff0e8d10680ee703ff1bc0bf2206fdb8a63 GIT binary patch literal 9876 zcmeHLeQXow89yiFlco(cZ6Pr2YTAjAo_*(>KkgJmLL3rrIE@mEg^I15@6O2ye_>w| z2LxEaSSeLYY4=e?w@xZ_p;M}bWm-QdAja5G)vhf=Q>LtpMOvh8P*%DT24&Ct+DR@X za(vRf_76L{yXW5b_x|4J{XNh7T<-R^wltNMR+bV3QRZ%TwGqTTFy|4)g>c#NmFit^ z`MuoX_PgCHh+?<_q+uRm01F`v8avOBPvk@M9yk_)jWFn_G!)?LQ(!Mi+0TQ$JcU7H z7(*UW2G>JySir7;>rptA?w-3<+7BpUWB0hu7;hqgWIY(8DL^vvV!hqrtA zj{09e*w8(=?@7mn6%{{O{nPs%>!x47Hvb^|P?hQFotsXbdgjd0UHgs=+&!?9yl%Mk z_lqx&86W=KO#R}Cs~3Ou)hm~sm;Y>7ef%Bv)#}2oH-bkNZf2hQ)_wV}jWrhE`r(=V z?>r*5960dF(#QY($<@zH=Z?Mg-Yu^1{Gl_%kDHf@dz5>_&s8Cd4AoC74xfxpV@l7X~PMZ={p-9rlXI% zlvg(RanbEvAB{XV-v2v)8-2HW!tw6eMdg1isI;CPU)gi$HzgN$6b@Yb<%zZ;|0?%u zLqoq^P(EB}8u-(}!J-Y9iPMkn37i`keWhyj)06*MysiEIMQ;_{xNz|J(7!g{AYSf& zb+jxJDSqmKlY zme$63APCB;K*ocCP>hc|jhL4Q8#SAaB!;M)oW>5wqlR!)CRr0}qN&EX(ql2MEG28B zlAmvLtw~Y9ozvK*suA97?(OY0_12ie(M~hNah#d9m@O6x5LB!$qzZ8=6kCQUk{m8M zCPtNrs)R!%;uL)0Zq;ctLO(g-UohhFOwxy9DHT8ub6kj+853;|2F=A7J+Pf5K^0trDg)_c9rjMt;cD8#(@FOcohK|Q{=T`;X!DS@ zfQOJbD>0bA4>u)uRJ*)6d6&EmD0nN8B;FLfASRa(6MAF``w3jhF0o4pb;?jZQt3L; zuFR4OOpTSZOSFSxC7VdGJ_w?ZWBia@k|;_x!RnCZ>FlwvU+op5a(yT02wFjU;wKcj z90#guI=Z(@Mo|F4D4L@f>z#s`QwuhunlZ8BTJsD|YB4}lWMEvf4T=}kLi1!XOlgM7 zP8NTuSY&Yqpw3OAOW&MaIk|KxpbMPKuAE%D6wn3EW!L{quF|R3DLDlH1@*$q(se`U z4S3Ni^nIhrgh?4ijg{PUS3%zRhZMXz z-_N*R_1^gKKZf5gIZ5BuQ2)Ag`+Fl*HxIwLz}tL!*>h5U-GgUWU8}s8sJO8fO^}C9 zBs$G$CNK(pfgt8INrak{F$u_wF@YH|mc(Xep6emcj8QXtmdF!$eP04{%~CTn^?05c zV=hHz%}PB&C$Y2A%)UeQkn4#e#%7CL&vwCBJ)Y|!Pa||hIqi6um9 z+^}d##iw`#`shp4p%eP5o8H9Ve#;RGf7T_^m~|L6CnE-w6S+qMl1mfqG{GdXvUdVB zW9KyQ96LKBZcX6H=48|WvP7Q7$Nz6J(JVDHQ;+AFF(!keL+GSJSt3t*^CckHBQ(b9 z@mvpi8ZA!ty9fQm*s+!!W55MlC>mF6)t zV?BDvb!d^CCrjj-rDkaA@H|7t)Pou%pCM@)`Lji?M`+!i>mk=6bS_0^73&K~k&EYR z40ZkMm(*P`Y|Kln{x(YL1UhN|14~oHNsP^`W~ZS^U>Xe|FHr(bb+|dRBuumQW^B{U z7?ZQl8mSqKn%T2Nu15>qp6emkEHyJTTjY9##@Ni<&1!MVD!Cq^b16cg-<>)11*C}M av;6$OEcpC#;ZQa5$lciD8d$w?=YIjyx;{q$ literal 0 HcmV?d00001 diff --git a/assets/meshes/cube.mtl b/assets/meshes/cube.mtl new file mode 100644 index 0000000..4d490e8 --- /dev/null +++ b/assets/meshes/cube.mtl @@ -0,0 +1,12 @@ +# Blender 4.1.1 MTL File: 'None' +# www.blender.org + +newmtl Material +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 diff --git a/assets/shaders/glsl/textured_3dtriangles_from_buffer.vert b/assets/shaders/glsl/textured_3dtriangles_from_buffer.vert new file mode 100644 index 0000000..d7a506d --- /dev/null +++ b/assets/shaders/glsl/textured_3dtriangles_from_buffer.vert @@ -0,0 +1,23 @@ +#version 460 + +layout(set = 1, binding = 0) +uniform Parameters +{ + mat4 u_worldToView; + mat4 u_viewToClip; +}; + +layout(location = 0) +in vec3 i_position; + +layout(location = 1) +in vec2 i_texCoord; + +layout(location = 0) +out vec2 o_texCoord; + +void main() +{ + gl_Position = u_viewToClip * u_worldToView * vec4(i_position, 1.0); + o_texCoord = i_texCoord; +} diff --git a/external/scons-plus-plus b/external/scons-plus-plus index fe8f329..c74fbb8 160000 --- a/external/scons-plus-plus +++ b/external/scons-plus-plus @@ -1 +1 @@ -Subproject commit fe8f329b3852b0492cee6fc3c7d5ed71cf761c07 +Subproject commit c74fbb8798a78227d4f53b508c079eae6245246d diff --git a/private/sdl_gpu_test/2_textured_cube/app.cpp b/private/sdl_gpu_test/2_textured_cube/app.cpp new file mode 100644 index 0000000..8f9faf2 --- /dev/null +++ b/private/sdl_gpu_test/2_textured_cube/app.cpp @@ -0,0 +1,176 @@ + +#include "./app.hpp" + +#include "../util/mesh.hpp" + +#include +#include +#include +#include +#include + +#include "../util/mesh.hpp" + +namespace sdl_gpu_test +{ +namespace +{ +struct VertexShaderParameters +{ + glm::mat4 worldToView; + glm::mat4 viewToClip; +}; +} + +void TexturedCubeApp::init(const AppInitArgs& args) +{ + Application::init(args); + + // create shaders + const sdlpp::GPUShader vertexShader = loadShader("shaders/glsl/textured_3dtriangles_from_buffer.vert.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::VERTEX, + .numUniformBuffers = 1 + }); + const sdlpp::GPUShader fragmentShader = loadShader("shaders/glsl/color_from_texture.frag.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::FRAGMENT, + .numSamplers = 1, + .numUniformBuffers = 1 + }); + + // create depth buffer + mDepthBuffer.create(mDevice, { + .format = sdlpp::GPUTextureFormat::D16_UNORM, + .usage = sdlpp::GPUTextureUsageFlags{.depthStencilTarget = true}, + .width = 1280, + .height = 720 + }); + + // create graphics pipeline + std::array colorTargetsDescs = { + sdlpp::GPUColorTargetDescription{ + .format = mDevice.getSwapchainTextureFormat(mWindow), + .blendState = { + .enableBlend = true, + .srcColorBlendfactor = sdlpp::GPUBlendFactor::SRC_ALPHA, + .dstColorBlendfactor = sdlpp::GPUBlendFactor::ONE_MINUS_SRC_ALPHA, + .colorBlendOp = sdlpp::GPUBlendOp::ADD, + .srcAlphaBlendfactor = sdlpp::GPUBlendFactor::ONE, + .dstAlphaBlendfactor = sdlpp::GPUBlendFactor::ZERO, + .alphaBlendOp = sdlpp::GPUBlendOp::ADD + } + } + }; + std::array vertexBindings = { + sdlpp::GPUVertexBinding{ + .index = 0, + .pitch = sizeof(Vertex) + } + }; + std::array vertexAttributes = { + sdlpp::GPUVertexAttribute{ + .location = 0, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT3, + .offset = offsetof(Vertex, pos) + }, + sdlpp::GPUVertexAttribute{ + .location = 1, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT2, + .offset = offsetof(Vertex, texcoord) + } + }; + mPipeline.create(mDevice, { + .vertexShader = vertexShader, + .fragmentShader = fragmentShader, + .vertexInputState = { + .vertexBindings = vertexBindings, + .vertexAttributes = vertexAttributes + }, + .rasterizerState = { + .cullMode = sdlpp::GPUCullMode::BACK + }, + .targetInfo = { + .colorTargetDescriptions = colorTargetsDescs, + .depthStencilFormat = sdlpp::GPUTextureFormat::D16_UNORM + } + }); + + // load the mesh + const Mesh mesh = loadMesh(mFileSystem.getPath("meshes/cube.obj")); + mNumVertices = static_cast(mesh.vertices.size()); + + // create vertex buffer + mVertexBuffer.create(mDevice, { + .usage = {.vertex = true}, + .size = static_cast(mesh.vertices.size() * sizeof(Vertex)) + }); + uploadVertexData(mVertexBuffer, std::span(mesh.vertices.begin(), mesh.vertices.end())); + + // create texture and sampler + sdlpp::GPUTextureCreateArgs textureArgs = { + .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, + .usage = {.sampler = true} + }; + mTexture = loadTexture("bitmaps/cube.png", textureArgs); + mSampler.create(mDevice, {}); +} + +void TexturedCubeApp::update(const AppUpdateArgs& args) +{ + Application::update(args); + + sdlpp::GPUCommandBuffer cmdBuffer = mDevice.acquireCommandBuffer(); + Uint32 swapchainWidth = 0, swapchainHeight = 0; + const sdlpp::GPUTexture swapchainTexture = cmdBuffer.acquireSwapchainTexture(mWindow, swapchainWidth, swapchainHeight); + + if (swapchainWidth != mLastSwapchainWidth || swapchainHeight != mLastSwapchainHeight) + { + mDepthBuffer.destroy(); + mDepthBuffer.create(mDevice, { + .format = sdlpp::GPUTextureFormat::D16_UNORM, + .usage = sdlpp::GPUTextureUsageFlags{.depthStencilTarget = true}, + .width = swapchainWidth, + .height = swapchainHeight + }); + mLastSwapchainWidth = swapchainWidth; + mLastSwapchainHeight = swapchainHeight; + } + + const VertexShaderParameters vertexShaderParameters = { + .worldToView = glm::lookAt(glm::vec3(2.f, 1.5f, 2.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 1.f, 0.f)) + * glm::rotate(glm::mat4(1.f), glm::radians(40.f * args.secondsSinceStart), glm::vec3(0.f, 1.f, 0.f)), + .viewToClip = glm::perspectiveFov( + /* fov = */ glm::radians(90.f), + /* width = */ static_cast(swapchainWidth), + /* height = */ static_cast(swapchainHeight), + /* zNear = */ 0.1f, + /* zFar = */ 100.f + ) + }; + + std::array colorTargets = {sdlpp::GPUColorTargetInfo{ + .texture = swapchainTexture, + .clearColor = {.r = 0.f, .g = 0.f, .b = 0.f, .a = 1.f}, + .loadOp = sdlpp::GPULoadOp::CLEAR, + }}; + sdlpp::GPURenderPass renderPass = cmdBuffer.beginRenderPass({ + .colorTargetInfos = colorTargets, + .depthStencilTargetInfo = sdlpp::GPUDepthStencilTargetInfo{ + .texture = mDepthBuffer, + .loadOp = sdlpp::GPULoadOp::CLEAR + } + }); + static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f); + cmdBuffer.pushFragmentUniformData(0, std::span(&WHITE, 1)); + cmdBuffer.pushVertexUniformData(0, std::span(&vertexShaderParameters, 1)); + renderPass.bindFragmentSampler({.texture = mTexture, .sampler = mSampler}); + renderPass.bindGraphicsPipeline(mPipeline); + renderPass.bindVertexBuffer({.buffer = mVertexBuffer}); + renderPass.drawPrimitives({.numVertices = mNumVertices}); + renderPass.end(); + cmdBuffer.submit(); +} +} diff --git a/private/sdl_gpu_test/2_textured_cube/app.hpp b/private/sdl_gpu_test/2_textured_cube/app.hpp new file mode 100644 index 0000000..3ffb38e --- /dev/null +++ b/private/sdl_gpu_test/2_textured_cube/app.hpp @@ -0,0 +1,28 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_2_TEXTURED_CUBE_APP_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_2_TEXTURED_CUBE_APP_HPP_INCLUDED 1 + +#include "../application.hpp" + +namespace sdl_gpu_test +{ +class TexturedCubeApp : public Application +{ +private: + sdlpp::GPUBuffer mVertexBuffer; + sdlpp::GPUTexture mDepthBuffer; + sdlpp::GPUGraphicsPipeline mPipeline; + sdlpp::GPUTexture mTexture; + sdlpp::GPUSampler mSampler; + Uint32 mNumVertices = 0; + Uint32 mLastSwapchainWidth = 1280; + Uint32 mLastSwapchainHeight = 720; +public: + void init(const AppInitArgs& args) override; + void update(const AppUpdateArgs& args) override; +}; +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_2_TEXTURED_CUBE_APP_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index f6826ad..b3a17d8 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -6,9 +6,11 @@ src_files = Split(""" application.cpp util/bitmap.cpp + util/mesh.cpp 0_triangle_with_texcoords/app.cpp 1_textured_quad/app.cpp + 2_textured_cube/app.cpp """) shader_files = env.Glob("#assets/shaders/glsl/*.frag") \ @@ -27,7 +29,8 @@ prog_app = env.UnityProgram( } }, 'spdlog': {}, - 'stb': {} + 'stb': {}, + 'tinyobjloader': {} } ) env.Default(prog_app) diff --git a/private/sdl_gpu_test/application.cpp b/private/sdl_gpu_test/application.cpp index f6bb0e5..0f6f0d8 100644 --- a/private/sdl_gpu_test/application.cpp +++ b/private/sdl_gpu_test/application.cpp @@ -1,6 +1,8 @@ #include "./application.hpp" +#include + #include #include #include @@ -48,9 +50,12 @@ void Application::update(const AppUpdateArgs&) void Application::run(std::span args) { + using clock_t = std::chrono::steady_clock; init({ .programArgs = args }); + + const clock_t::time_point startTime = clock_t::now(); while (mRunning) { std::optional event; @@ -61,7 +66,11 @@ void Application::run(std::span args) [](const auto&) {} // default handler }, *event); } - update({}); + + const clock_t::time_point frameTime = clock_t::now(); + update({ + .secondsSinceStart = std::chrono::duration_cast>(frameTime - startTime).count() + }); } cleanup({}); } diff --git a/private/sdl_gpu_test/application.hpp b/private/sdl_gpu_test/application.hpp index 348d4f8..51a30b9 100644 --- a/private/sdl_gpu_test/application.hpp +++ b/private/sdl_gpu_test/application.hpp @@ -26,7 +26,7 @@ struct AppCleanupArgs struct AppUpdateArgs { - + float secondsSinceStart = 0.f; }; class Application diff --git a/private/sdl_gpu_test/main.cpp b/private/sdl_gpu_test/main.cpp index 5e1ab9c..89cd426 100644 --- a/private/sdl_gpu_test/main.cpp +++ b/private/sdl_gpu_test/main.cpp @@ -1,6 +1,7 @@ #include "./0_triangle_with_texcoords/app.hpp" #include "./1_textured_quad/app.hpp" +#include "./2_textured_cube/app.hpp" #include #include @@ -23,7 +24,8 @@ int main(int argc, char* argv[]) { // make sure app is destructed before shutting down SDL // std::unique_ptr app = std::make_unique(); - std::unique_ptr app = std::make_unique(); + // std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); app->run(std::span(const_cast(argv), argc)); } catch (std::exception& exception) diff --git a/private/sdl_gpu_test/sdlpp/gpu.hpp b/private/sdl_gpu_test/sdlpp/gpu.hpp index 802e86b..b4bcf4d 100644 --- a/private/sdl_gpu_test/sdlpp/gpu.hpp +++ b/private/sdl_gpu_test/sdlpp/gpu.hpp @@ -433,7 +433,6 @@ static_assert(sizeof(GPUColorTargetDescription) == sizeof(SDL_GPUColorTargetDesc struct GPUGraphicsPipelineTargetInfo { std::span colorTargetDescriptions; - bool hasDepthStencilTarget = false; GPUTextureFormat depthStencilFormat = GPUTextureFormat::INVALID; explicit operator SDL_GpuGraphicsPipelineTargetInfo() const noexcept @@ -441,7 +440,7 @@ struct GPUGraphicsPipelineTargetInfo return { .color_target_descriptions = std::bit_cast(colorTargetDescriptions.data()), .num_color_targets = static_cast(colorTargetDescriptions.size()), - .has_depth_stencil_target = hasDepthStencilTarget, + .has_depth_stencil_target = depthStencilFormat != GPUTextureFormat::INVALID, .depth_stencil_format = static_cast(depthStencilFormat) }; } @@ -843,6 +842,17 @@ public: return texture; } + template + void pushVertexUniformData(Uint32 slotIndex, std::span data) const noexcept + { + SDL_PushGPUVertexUniformData( + /* command_buffer = */ mHandle, + /* slot_index = */ slotIndex, + /* data = */ data.data(), + /* length = */ data.size_bytes() + ); + } + template void pushFragmentUniformData(Uint32 slotIndex, std::span data) const noexcept { diff --git a/private/sdl_gpu_test/util/mesh.cpp b/private/sdl_gpu_test/util/mesh.cpp new file mode 100644 index 0000000..446bd67 --- /dev/null +++ b/private/sdl_gpu_test/util/mesh.cpp @@ -0,0 +1,75 @@ + +#include "./mesh.hpp" + +#include + +namespace sdl_gpu_test +{ +Mesh loadMesh(const mijin::PathReference& path) +{ + std::unique_ptr stream; + mijin::throwOnError(path.open(mijin::FileOpenMode::READ, stream)); + mijin::IOStreamAdapter adapter(*stream); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warning; + std::string error; + + const bool success = tinyobj::LoadObj( + /* attrib = */ &attrib, + /* shapes = */ &shapes, + /* materials = */ &materials, + /* warn = */ &warning, + /* err = */ &error, + /* inStream = */ &adapter + ); + mijin::ensure(success, "Parsing OBJ file failed."); + + Mesh mesh; + for (const tinyobj::shape_t& shape : shapes) + { + std::size_t indexOffset = 0; + for (const std::size_t faceVertices : shape.mesh.num_face_vertices) + { + if (faceVertices != 3) + { + // we only deal with triangles + indexOffset += faceVertices; + continue; + } + for (int vertexIdx = 0; vertexIdx < 3; ++vertexIdx) + { + const tinyobj::index_t& index = shape.mesh.indices[indexOffset + vertexIdx]; + mesh.vertices.push_back({ + .pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }, + .normal = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }, + .texcoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.f - attrib.texcoords[2 * index.texcoord_index + 1] // obj UV is weird + }, + .color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2], + 1.f + } + }); + } + + indexOffset += 3; + } + } + return mesh; +} +} \ No newline at end of file diff --git a/private/sdl_gpu_test/util/mesh.hpp b/private/sdl_gpu_test/util/mesh.hpp new file mode 100644 index 0000000..1e1b797 --- /dev/null +++ b/private/sdl_gpu_test/util/mesh.hpp @@ -0,0 +1,34 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_MESH_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_MESH_HPP_INCLUDED 1 + +#include + +#include +#include +#include +#include +#include + +namespace sdl_gpu_test +{ +struct Vertex +{ + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 texcoord; + glm::vec4 color; +}; + +struct Mesh +{ + std::vector vertices; +}; + +[[nodiscard]] +Mesh loadMesh(const mijin::PathReference& path); +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_MESH_HPP_INCLUDED)