diff --git a/assets/bitmaps/cube.png b/assets/meshes/cube.png similarity index 100% rename from assets/bitmaps/cube.png rename to assets/meshes/cube.png diff --git a/assets/scenes/test_scene.yml b/assets/scenes/test_scene.yml new file mode 100644 index 0000000..c2c8dfd --- /dev/null +++ b/assets/scenes/test_scene.yml @@ -0,0 +1,7 @@ +nodes: + - type: mesh + translation: [0, 0, 0] + mesh: meshes/cube.obj + - type: mesh + translation: [1, 0, -10] + mesh: meshes/cube.obj \ No newline at end of file diff --git a/assets/shaders/glsl/scene_renderer.vert b/assets/shaders/glsl/scene_renderer.vert new file mode 100644 index 0000000..8908174 --- /dev/null +++ b/assets/shaders/glsl/scene_renderer.vert @@ -0,0 +1,29 @@ +#version 460 + +layout(set = 1, binding = 0) +uniform PerSceneParameters +{ + mat4 u_worldToView; + mat4 u_viewToClip; +}; + +layout(set = 1, binding = 1) +uniform PerModelParameters +{ + mat4 u_modelToWorld; +}; + +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 * u_modelToWorld * vec4(i_position, 1.0); + o_texCoord = i_texCoord; +} diff --git a/private/sdl_gpu_test/4_textured_cube/app.cpp b/private/sdl_gpu_test/4_textured_cube/app.cpp index 8f9faf2..52a0d1f 100644 --- a/private/sdl_gpu_test/4_textured_cube/app.cpp +++ b/private/sdl_gpu_test/4_textured_cube/app.cpp @@ -114,7 +114,7 @@ void TexturedCubeApp::init(const AppInitArgs& args) .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .usage = {.sampler = true} }; - mTexture = loadTexture("bitmaps/cube.png", textureArgs); + mTexture = loadTexture("meshes/cube.png", textureArgs); mSampler.create(mDevice, {}); } diff --git a/private/sdl_gpu_test/5_input/app.cpp b/private/sdl_gpu_test/5_input/app.cpp index b1f4a76..8d93a60 100644 --- a/private/sdl_gpu_test/5_input/app.cpp +++ b/private/sdl_gpu_test/5_input/app.cpp @@ -117,7 +117,7 @@ void InputApp::init(const AppInitArgs& args) .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .usage = {.sampler = true} }; - mTexture = loadTexture("bitmaps/cube.png", textureArgs); + mTexture = loadTexture("meshes/cube.png", textureArgs); mSampler.create(mDevice, {}); // open gamepad diff --git a/private/sdl_gpu_test/6_ui/app.cpp b/private/sdl_gpu_test/6_ui/app.cpp index f27e7d7..23098a0 100644 --- a/private/sdl_gpu_test/6_ui/app.cpp +++ b/private/sdl_gpu_test/6_ui/app.cpp @@ -60,7 +60,7 @@ void UIApp::init(const AppInitArgs& args) .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .usage = {.sampler = true} }; - mTexture = loadTexture("bitmaps/cube.png", textureArgs); + mTexture = loadTexture("meshes/cube.png", textureArgs); mSampler.create(mDevice, {}); // open gamepad diff --git a/private/sdl_gpu_test/7_3d_scene/app.cpp b/private/sdl_gpu_test/7_3d_scene/app.cpp index c4bf8ff..7139269 100644 --- a/private/sdl_gpu_test/7_3d_scene/app.cpp +++ b/private/sdl_gpu_test/7_3d_scene/app.cpp @@ -9,6 +9,7 @@ #include #include "../scene/mesh.hpp" +#include "../scene/loader.hpp" #include "../sdlpp/keyboard.hpp" #include "../util/bitmap.hpp" #include "../util/mesh.hpp" @@ -64,7 +65,8 @@ void ThreeDSceneApp::init(const AppInitArgs& args) // init the scene mScene.init({.renderer = &mSceneRenderer}); - mScene.getRootNode().emplaceChild({.mesh = "meshes/cube.obj", .texture = "bitmaps/cube.png"}); + loadScene("scenes/test_scene.yml", mScene); + // mScene.getRootNode().emplaceChild({.mesh = "meshes/cube.obj", .texture = "meshes/cube.png"}); mButton->clicked.connect([&]() { diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index 7843eac..ddddeb3 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -9,6 +9,7 @@ src_files = Split(""" gui/label.cpp gui/ui_renderer.cpp gui/widget.cpp + scene/loader.cpp scene/mesh.cpp scene/scene.cpp scene/scene_renderer.cpp @@ -45,7 +46,8 @@ prog_app = env.UnityProgram( }, 'spdlog': {}, 'stb': {}, - 'tinyobjloader': {} + 'tinyobjloader': {}, + 'yaml-cpp': {} } ) env.Default(prog_app) diff --git a/private/sdl_gpu_test/application.cpp b/private/sdl_gpu_test/application.cpp index 2ee2cb9..f36d6b8 100644 --- a/private/sdl_gpu_test/application.cpp +++ b/private/sdl_gpu_test/application.cpp @@ -6,6 +6,7 @@ #include #include +#include "./scene/loader.hpp" #include "./util/bitmap.hpp" #include "./util/spdlog_wrapper.hpp" @@ -125,6 +126,11 @@ sdlpp::GPUTexture Application::loadTexture(const fs::path& path, sdlpp::GPUTextu return texture; } +void Application::loadScene(const fs::path& path, Scene& outScene) +{ + sdl_gpu_test::loadScene(mFileSystem.getPath(path), outScene); +} + void Application::uploadTextureData(const sdlpp::GPUTexture& texture, const Bitmap& bitmap) const { sdlpp::GPUTransferBuffer transferBuffer; diff --git a/private/sdl_gpu_test/application.hpp b/private/sdl_gpu_test/application.hpp index f271685..a9f304b 100644 --- a/private/sdl_gpu_test/application.hpp +++ b/private/sdl_gpu_test/application.hpp @@ -77,6 +77,8 @@ public: [[nodiscard]] sdlpp::GPUTexture loadTexture(const fs::path& path, sdlpp::GPUTextureCreateArgs& inout_args); + void loadScene(const fs::path& path, class Scene& outScene); + void uploadTextureData(const sdlpp::GPUTexture& texture, const struct Bitmap& bitmap) const; template diff --git a/private/sdl_gpu_test/scene/loader.cpp b/private/sdl_gpu_test/scene/loader.cpp new file mode 100644 index 0000000..21a9625 --- /dev/null +++ b/private/sdl_gpu_test/scene/loader.cpp @@ -0,0 +1,106 @@ + +#include "./loader.hpp" + +#include +#include "./mesh.hpp" + +namespace sdl_gpu_test +{ +namespace +{ +void loadCommonNodeProperties(const YAML::Node& yaml, SceneNode& node) +{ + const YAML::Node& translationNode = yaml["translation"]; + glm::vec3 translation = {0.f, 0.f, 0.f}; + if (translationNode.IsDefined()) + { + mijin::ensure(translationNode.IsSequence() && translationNode.size() == 3 + && translationNode[0].IsScalar() && translationNode[1].IsScalar() && translationNode[2].IsScalar(), + "Invalid scene YAML: node translation must be sequence of three scalars."); + translation.x = translationNode[0].as(); + translation.y = translationNode[1].as(); + translation.z = translationNode[2].as(); + } + + node.setTransform(Transform3D::make(translation)); +} + +std::unique_ptr loadMeshNode(const YAML::Node& yaml) +{ + MIJIN_ASSERT(yaml.IsMap(), "yaml must be a map"); + + const YAML::Node& meshNode = yaml["mesh"]; + mijin::ensure(meshNode.IsScalar(), "Invalid scene YAML: invalid or missing mesh property for mesh node."); + + std::string meshPath = meshNode.as(); + std::string texturePath; + + const YAML::Node& textureNode = yaml["texture"]; + if (!textureNode.IsDefined()) + { + const std::string::size_type dotPos = meshPath.rfind('.'); + texturePath = std::string_view(meshPath).substr(0, dotPos); + texturePath.append(".png"); + } + else + { + mijin::ensure(textureNode.IsScalar(), "Invalid scene YAML: invalid texture property for mesh node."); + texturePath = textureNode.as(); + } + + return std::make_unique(MeshNodeCreateArgs{ + .mesh = std::move(meshPath), .texture = std::move(texturePath) + }); +} + +void loadSceneNodes(const YAML::Node& nodesNode, SceneNode& parentNode) +{ + MIJIN_ASSERT(nodesNode.IsSequence(), "nodesNode must be a sequence"); + + for (const YAML::Node& nodeNode : nodesNode) + { + mijin::ensure(nodeNode.IsMap(), "Invalid scene YAML: scene node is not a sequence."); + + std::unique_ptr newNode; + const YAML::Node& typeNode = nodeNode["type"]; + if (!typeNode.IsDefined()) + { + newNode = std::make_unique(); + } + else + { + mijin::ensure(typeNode.IsScalar(), "Invalid scene YAML: scene node type not a scalar."); + + const std::string type = typeNode.as(); + if (type == "mesh") + { + newNode = loadMeshNode(nodeNode); + } + else + { + throw std::runtime_error(std::format("Invalid scene YAML: invalid scene node type '{}'.", type)); + } + } + loadCommonNodeProperties(nodeNode, *newNode); + parentNode.addChild(std::move(newNode)); + } +} +} + +void loadScene(const mijin::PathReference& path, Scene& outScene) +{ + std::unique_ptr stream; + mijin::throwOnError(path.open(mijin::FileOpenMode::READ, stream)); + + std::string content; + mijin::throwOnError(stream->readAsString(content)); + + const YAML::Node root = YAML::Load(content); + mijin::ensure(root.IsMap(), "Invalid scene YAML: root is not a map."); + + const YAML::Node& nodesNode = root["nodes"]; + mijin::ensure(nodesNode.IsSequence(), "Invalid scene YAML: nodes missing or not a sequence."); + + loadSceneNodes(nodesNode, outScene.getRootNode()); +} +} diff --git a/private/sdl_gpu_test/scene/loader.hpp b/private/sdl_gpu_test/scene/loader.hpp new file mode 100644 index 0000000..20a54c9 --- /dev/null +++ b/private/sdl_gpu_test/scene/loader.hpp @@ -0,0 +1,16 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SCENE_LOADER_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SCENE_LOADER_HPP_INCLUDED 1 + +#include + +#include "./scene.hpp" + +namespace sdl_gpu_test +{ +void loadScene(const mijin::PathReference& path, Scene& outScene); +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SCENE_LOADER_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/scene/mesh.cpp b/private/sdl_gpu_test/scene/mesh.cpp index 99c4079..26023b6 100644 --- a/private/sdl_gpu_test/scene/mesh.cpp +++ b/private/sdl_gpu_test/scene/mesh.cpp @@ -8,6 +8,18 @@ MeshNode::MeshNode(MeshNodeCreateArgs args) : mMeshPath(std::move(args.mesh)), m } +Transform3D MeshNode::getCombinedTransform() const noexcept +{ + Transform3D result = mTransform; + + for (SceneNode* parent = mParent; parent != nullptr; parent = parent->getParent()) + { + result = parent->getTransform().apply(result); + } + + return result; +} + void MeshNode::setMesh(std::string resourcePath) { mMeshPath = std::move(resourcePath); @@ -68,10 +80,10 @@ void MeshNode::updateSceneMesh() if (mMeshId == SceneRenderer::UNSET_MESH_ID || !mScene->getRenderer().updateRenderListEntry(mMeshId, { .mesh = mMesh, .texture = mTexture, - .transform = mTransform + .transform = getCombinedTransform() })) { - mScene->getRenderer().addMeshToRenderList(mMesh, mTexture, mTransform, mMeshId); + mScene->getRenderer().addMeshToRenderList(mMesh, mTexture, getCombinedTransform(), mMeshId); } } } diff --git a/private/sdl_gpu_test/scene/mesh.hpp b/private/sdl_gpu_test/scene/mesh.hpp index 4e5f849..64478a7 100644 --- a/private/sdl_gpu_test/scene/mesh.hpp +++ b/private/sdl_gpu_test/scene/mesh.hpp @@ -29,6 +29,9 @@ private: public: MeshNode(MeshNodeCreateArgs args); + [[nodiscard]] + Transform3D getCombinedTransform() const noexcept; + void setMesh(std::string resourcePath); void setTexture(std::string resourcePath); diff --git a/private/sdl_gpu_test/scene/scene.hpp b/private/sdl_gpu_test/scene/scene.hpp index ce326e2..562d0ee 100644 --- a/private/sdl_gpu_test/scene/scene.hpp +++ b/private/sdl_gpu_test/scene/scene.hpp @@ -31,6 +31,14 @@ protected: public: virtual ~SceneNode() noexcept = default; + [[nodiscard]] + SceneNode* getParent() const noexcept { return mParent; } + + [[nodiscard]] + const Transform3D& getTransform() const noexcept { return mTransform; } + + void setTransform(const Transform3D& transform) noexcept { mTransform = transform; } + virtual void handleEnteredScene(); SceneNode* addChild(scene_node_ptr_t&& node); diff --git a/private/sdl_gpu_test/scene/scene_renderer.cpp b/private/sdl_gpu_test/scene/scene_renderer.cpp index 6e28706..99508e2 100644 --- a/private/sdl_gpu_test/scene/scene_renderer.cpp +++ b/private/sdl_gpu_test/scene/scene_renderer.cpp @@ -13,11 +13,16 @@ namespace sdl_gpu_test { namespace { - struct VertexShaderParameters - { - glm::mat4 worldToView; - glm::mat4 viewToClip; - }; +struct PerSceneParameters +{ + glm::mat4 worldToView; + glm::mat4 viewToClip; +}; + +struct PerModelParameters +{ + glm::mat4 modelToWorld; +}; } void SceneRenderer::init(Application& application) { @@ -76,7 +81,7 @@ std::shared_ptr SceneRenderer::getTexture(const std::string& resou void SceneRenderer::render(const SceneRendererRenderArgs& args) { - VertexShaderParameters vertexShaderParameters = { + PerSceneParameters perSceneParameters = { // note that we are transforming the world, but are passed the camera transform, so invert it .worldToView = glm::translate( glm::transpose(glm::yawPitchRoll(args.camera.yaw, args.camera.pitch, args.camera.roll)), @@ -107,10 +112,14 @@ void SceneRenderer::render(const SceneRendererRenderArgs& args) static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f); + args.cmdBuffer->pushVertexUniformData(0, std::span(&perSceneParameters, 1)); for (const RenderListEntry& entry : mRenderList) { + PerModelParameters perModelParameters = { + .modelToWorld = entry.transform.matrix + }; args.cmdBuffer->pushFragmentUniformData(0, std::span(&WHITE, 1)); - args.cmdBuffer->pushVertexUniformData(0, std::span(&vertexShaderParameters, 1)); + args.cmdBuffer->pushVertexUniformData(1, std::span(&perModelParameters, 1)); renderPass.bindFragmentSampler({.texture = entry.texture->texture, .sampler = mSampler}); renderPass.bindVertexBuffer({.buffer = entry.mesh->vertexBuffer}); renderPass.drawPrimitives({.numVertices = static_cast(entry.mesh->numVertices)}); @@ -187,10 +196,10 @@ bool SceneRenderer::updateRenderListEntry(mesh_id_t meshId, const RenderListUpda void SceneRenderer::createPipeline() { // create shaders - const sdlpp::GPUShader vertexShader = mApplication->loadShader("shaders/glsl/textured_3dtriangles_from_buffer.vert.spv", { + const sdlpp::GPUShader vertexShader = mApplication->loadShader("shaders/glsl/scene_renderer.vert.spv", { .format = sdlpp::GPUShaderFormat::SPIRV, .stage = sdlpp::GPUShaderStage::VERTEX, - .numUniformBuffers = 1 + .numUniformBuffers = 2 }); const sdlpp::GPUShader fragmentShader = mApplication->loadShader("shaders/glsl/color_from_texture.frag.spv", { .format = sdlpp::GPUShaderFormat::SPIRV, @@ -242,6 +251,10 @@ void SceneRenderer::createPipeline() .rasterizerState = { .cullMode = sdlpp::GPUCullMode::BACK }, + .depthStencilState = { + .enableDepthTest = true, + .enableDepthWrite = true + }, .targetInfo = { .colorTargetDescriptions = colorTargetsDescs, .depthStencilFormat = sdlpp::GPUTextureFormat::D16_UNORM diff --git a/private/sdl_gpu_test/sdlpp/gpu.hpp b/private/sdl_gpu_test/sdlpp/gpu.hpp index 813de24..3e2c467 100644 --- a/private/sdl_gpu_test/sdlpp/gpu.hpp +++ b/private/sdl_gpu_test/sdlpp/gpu.hpp @@ -464,12 +464,12 @@ static_assert(sizeof(GPUColorTargetInfo) == sizeof(SDL_GPUColorTargetInfo) struct GPUDepthStencilTargetInfo { SDL_GPUTexture* texture = nullptr; - float clearDepth = 0.f; + float clearDepth = 1.f; GPULoadOp loadOp = GPULoadOp::LOAD; GPUStoreOp storeOp = GPUStoreOp::STORE; GPULoadOp stencilLoadOp = GPULoadOp::LOAD; GPUStoreOp stencilStoreOp = GPUStoreOp::STORE; - bool cycle = false; + bool cycle = true; Uint8 clearStencil; }; static_assert(sizeof(GPUDepthStencilTargetInfo) == sizeof(SDL_GPUDepthStencilTargetInfo) diff --git a/private/sdl_gpu_test/util/mesh.cpp b/private/sdl_gpu_test/util/mesh.cpp index 446bd67..c2850dc 100644 --- a/private/sdl_gpu_test/util/mesh.cpp +++ b/private/sdl_gpu_test/util/mesh.cpp @@ -1,6 +1,7 @@ #include "./mesh.hpp" +#include #include namespace sdl_gpu_test diff --git a/private/sdl_gpu_test/util/mesh.hpp b/private/sdl_gpu_test/util/mesh.hpp index 1e1b797..eac9811 100644 --- a/private/sdl_gpu_test/util/mesh.hpp +++ b/private/sdl_gpu_test/util/mesh.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace sdl_gpu_test