Implemented scene loading. (Still problems with depth testing, WIP).

This commit is contained in:
Patrick 2024-09-30 17:37:50 +02:00
parent e92f414e02
commit c904353baf
19 changed files with 225 additions and 19 deletions

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -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

View File

@ -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;
}

View File

@ -114,7 +114,7 @@ void TexturedCubeApp::init(const AppInitArgs& args)
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
.usage = {.sampler = true} .usage = {.sampler = true}
}; };
mTexture = loadTexture("bitmaps/cube.png", textureArgs); mTexture = loadTexture("meshes/cube.png", textureArgs);
mSampler.create(mDevice, {}); mSampler.create(mDevice, {});
} }

View File

@ -117,7 +117,7 @@ void InputApp::init(const AppInitArgs& args)
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
.usage = {.sampler = true} .usage = {.sampler = true}
}; };
mTexture = loadTexture("bitmaps/cube.png", textureArgs); mTexture = loadTexture("meshes/cube.png", textureArgs);
mSampler.create(mDevice, {}); mSampler.create(mDevice, {});
// open gamepad // open gamepad

View File

@ -60,7 +60,7 @@ void UIApp::init(const AppInitArgs& args)
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
.usage = {.sampler = true} .usage = {.sampler = true}
}; };
mTexture = loadTexture("bitmaps/cube.png", textureArgs); mTexture = loadTexture("meshes/cube.png", textureArgs);
mSampler.create(mDevice, {}); mSampler.create(mDevice, {});
// open gamepad // open gamepad

View File

@ -9,6 +9,7 @@
#include <glm/gtx/norm.hpp> #include <glm/gtx/norm.hpp>
#include "../scene/mesh.hpp" #include "../scene/mesh.hpp"
#include "../scene/loader.hpp"
#include "../sdlpp/keyboard.hpp" #include "../sdlpp/keyboard.hpp"
#include "../util/bitmap.hpp" #include "../util/bitmap.hpp"
#include "../util/mesh.hpp" #include "../util/mesh.hpp"
@ -64,7 +65,8 @@ void ThreeDSceneApp::init(const AppInitArgs& args)
// init the scene // init the scene
mScene.init({.renderer = &mSceneRenderer}); mScene.init({.renderer = &mSceneRenderer});
mScene.getRootNode().emplaceChild<MeshNode>({.mesh = "meshes/cube.obj", .texture = "bitmaps/cube.png"}); loadScene("scenes/test_scene.yml", mScene);
// mScene.getRootNode().emplaceChild<MeshNode>({.mesh = "meshes/cube.obj", .texture = "meshes/cube.png"});
mButton->clicked.connect([&]() mButton->clicked.connect([&]()
{ {

View File

@ -9,6 +9,7 @@ src_files = Split("""
gui/label.cpp gui/label.cpp
gui/ui_renderer.cpp gui/ui_renderer.cpp
gui/widget.cpp gui/widget.cpp
scene/loader.cpp
scene/mesh.cpp scene/mesh.cpp
scene/scene.cpp scene/scene.cpp
scene/scene_renderer.cpp scene/scene_renderer.cpp
@ -45,7 +46,8 @@ prog_app = env.UnityProgram(
}, },
'spdlog': {}, 'spdlog': {},
'stb': {}, 'stb': {},
'tinyobjloader': {} 'tinyobjloader': {},
'yaml-cpp': {}
} }
) )
env.Default(prog_app) env.Default(prog_app)

View File

@ -6,6 +6,7 @@
#include <mijin/util/variant.hpp> #include <mijin/util/variant.hpp>
#include <mijin/virtual_filesystem/relative.hpp> #include <mijin/virtual_filesystem/relative.hpp>
#include "./scene/loader.hpp"
#include "./util/bitmap.hpp" #include "./util/bitmap.hpp"
#include "./util/spdlog_wrapper.hpp" #include "./util/spdlog_wrapper.hpp"
@ -125,6 +126,11 @@ sdlpp::GPUTexture Application::loadTexture(const fs::path& path, sdlpp::GPUTextu
return texture; 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 void Application::uploadTextureData(const sdlpp::GPUTexture& texture, const Bitmap& bitmap) const
{ {
sdlpp::GPUTransferBuffer transferBuffer; sdlpp::GPUTransferBuffer transferBuffer;

View File

@ -77,6 +77,8 @@ public:
[[nodiscard]] [[nodiscard]]
sdlpp::GPUTexture loadTexture(const fs::path& path, sdlpp::GPUTextureCreateArgs& inout_args); 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; void uploadTextureData(const sdlpp::GPUTexture& texture, const struct Bitmap& bitmap) const;
template<typename TVertex> template<typename TVertex>

View File

@ -0,0 +1,106 @@
#include "./loader.hpp"
#include <yaml-cpp/yaml.h>
#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<float>();
translation.y = translationNode[1].as<float>();
translation.z = translationNode[2].as<float>();
}
node.setTransform(Transform3D::make(translation));
}
std::unique_ptr<MeshNode> 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>();
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<std::string>();
}
return std::make_unique<MeshNode>(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<SceneNode> newNode;
const YAML::Node& typeNode = nodeNode["type"];
if (!typeNode.IsDefined())
{
newNode = std::make_unique<SceneNode>();
}
else
{
mijin::ensure(typeNode.IsScalar(), "Invalid scene YAML: scene node type not a scalar.");
const std::string type = typeNode.as<std::string>();
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<mijin::Stream> 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());
}
}

View File

@ -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 <mijin/virtual_filesystem/filesystem.hpp>
#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)

View File

@ -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) void MeshNode::setMesh(std::string resourcePath)
{ {
mMeshPath = std::move(resourcePath); mMeshPath = std::move(resourcePath);
@ -68,10 +80,10 @@ void MeshNode::updateSceneMesh()
if (mMeshId == SceneRenderer::UNSET_MESH_ID || !mScene->getRenderer().updateRenderListEntry(mMeshId, { if (mMeshId == SceneRenderer::UNSET_MESH_ID || !mScene->getRenderer().updateRenderListEntry(mMeshId, {
.mesh = mMesh, .mesh = mMesh,
.texture = mTexture, .texture = mTexture,
.transform = mTransform .transform = getCombinedTransform()
})) }))
{ {
mScene->getRenderer().addMeshToRenderList(mMesh, mTexture, mTransform, mMeshId); mScene->getRenderer().addMeshToRenderList(mMesh, mTexture, getCombinedTransform(), mMeshId);
} }
} }
} }

View File

@ -29,6 +29,9 @@ private:
public: public:
MeshNode(MeshNodeCreateArgs args); MeshNode(MeshNodeCreateArgs args);
[[nodiscard]]
Transform3D getCombinedTransform() const noexcept;
void setMesh(std::string resourcePath); void setMesh(std::string resourcePath);
void setTexture(std::string resourcePath); void setTexture(std::string resourcePath);

View File

@ -31,6 +31,14 @@ protected:
public: public:
virtual ~SceneNode() noexcept = default; 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(); virtual void handleEnteredScene();
SceneNode* addChild(scene_node_ptr_t&& node); SceneNode* addChild(scene_node_ptr_t&& node);

View File

@ -13,11 +13,16 @@ namespace sdl_gpu_test
{ {
namespace namespace
{ {
struct VertexShaderParameters struct PerSceneParameters
{ {
glm::mat4 worldToView; glm::mat4 worldToView;
glm::mat4 viewToClip; glm::mat4 viewToClip;
}; };
struct PerModelParameters
{
glm::mat4 modelToWorld;
};
} }
void SceneRenderer::init(Application& application) void SceneRenderer::init(Application& application)
{ {
@ -76,7 +81,7 @@ std::shared_ptr<SceneTexture> SceneRenderer::getTexture(const std::string& resou
void SceneRenderer::render(const SceneRendererRenderArgs& args) 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 // note that we are transforming the world, but are passed the camera transform, so invert it
.worldToView = glm::translate( .worldToView = glm::translate(
glm::transpose(glm::yawPitchRoll(args.camera.yaw, args.camera.pitch, args.camera.roll)), 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); static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f);
args.cmdBuffer->pushVertexUniformData(0, std::span<const PerSceneParameters>(&perSceneParameters, 1));
for (const RenderListEntry& entry : mRenderList) for (const RenderListEntry& entry : mRenderList)
{ {
PerModelParameters perModelParameters = {
.modelToWorld = entry.transform.matrix
};
args.cmdBuffer->pushFragmentUniformData(0, std::span(&WHITE, 1)); args.cmdBuffer->pushFragmentUniformData(0, std::span(&WHITE, 1));
args.cmdBuffer->pushVertexUniformData(0, std::span<const VertexShaderParameters>(&vertexShaderParameters, 1)); args.cmdBuffer->pushVertexUniformData(1, std::span<const PerModelParameters>(&perModelParameters, 1));
renderPass.bindFragmentSampler({.texture = entry.texture->texture, .sampler = mSampler}); renderPass.bindFragmentSampler({.texture = entry.texture->texture, .sampler = mSampler});
renderPass.bindVertexBuffer({.buffer = entry.mesh->vertexBuffer}); renderPass.bindVertexBuffer({.buffer = entry.mesh->vertexBuffer});
renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(entry.mesh->numVertices)}); renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(entry.mesh->numVertices)});
@ -187,10 +196,10 @@ bool SceneRenderer::updateRenderListEntry(mesh_id_t meshId, const RenderListUpda
void SceneRenderer::createPipeline() void SceneRenderer::createPipeline()
{ {
// create shaders // 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, .format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::VERTEX, .stage = sdlpp::GPUShaderStage::VERTEX,
.numUniformBuffers = 1 .numUniformBuffers = 2
}); });
const sdlpp::GPUShader fragmentShader = mApplication->loadShader("shaders/glsl/color_from_texture.frag.spv", { const sdlpp::GPUShader fragmentShader = mApplication->loadShader("shaders/glsl/color_from_texture.frag.spv", {
.format = sdlpp::GPUShaderFormat::SPIRV, .format = sdlpp::GPUShaderFormat::SPIRV,
@ -242,6 +251,10 @@ void SceneRenderer::createPipeline()
.rasterizerState = { .rasterizerState = {
.cullMode = sdlpp::GPUCullMode::BACK .cullMode = sdlpp::GPUCullMode::BACK
}, },
.depthStencilState = {
.enableDepthTest = true,
.enableDepthWrite = true
},
.targetInfo = { .targetInfo = {
.colorTargetDescriptions = colorTargetsDescs, .colorTargetDescriptions = colorTargetsDescs,
.depthStencilFormat = sdlpp::GPUTextureFormat::D16_UNORM .depthStencilFormat = sdlpp::GPUTextureFormat::D16_UNORM

View File

@ -464,12 +464,12 @@ static_assert(sizeof(GPUColorTargetInfo) == sizeof(SDL_GPUColorTargetInfo)
struct GPUDepthStencilTargetInfo struct GPUDepthStencilTargetInfo
{ {
SDL_GPUTexture* texture = nullptr; SDL_GPUTexture* texture = nullptr;
float clearDepth = 0.f; float clearDepth = 1.f;
GPULoadOp loadOp = GPULoadOp::LOAD; GPULoadOp loadOp = GPULoadOp::LOAD;
GPUStoreOp storeOp = GPUStoreOp::STORE; GPUStoreOp storeOp = GPUStoreOp::STORE;
GPULoadOp stencilLoadOp = GPULoadOp::LOAD; GPULoadOp stencilLoadOp = GPULoadOp::LOAD;
GPUStoreOp stencilStoreOp = GPUStoreOp::STORE; GPUStoreOp stencilStoreOp = GPUStoreOp::STORE;
bool cycle = false; bool cycle = true;
Uint8 clearStencil; Uint8 clearStencil;
}; };
static_assert(sizeof(GPUDepthStencilTargetInfo) == sizeof(SDL_GPUDepthStencilTargetInfo) static_assert(sizeof(GPUDepthStencilTargetInfo) == sizeof(SDL_GPUDepthStencilTargetInfo)

View File

@ -1,6 +1,7 @@
#include "./mesh.hpp" #include "./mesh.hpp"
#include <mijin/io/stlstream.hpp>
#include <tiny_obj_loader.h> #include <tiny_obj_loader.h>
namespace sdl_gpu_test namespace sdl_gpu_test

View File

@ -9,7 +9,6 @@
#include <glm/vec2.hpp> #include <glm/vec2.hpp>
#include <glm/vec3.hpp> #include <glm/vec3.hpp>
#include <glm/vec4.hpp> #include <glm/vec4.hpp>
#include <mijin/io/stlstream.hpp>
#include <mijin/virtual_filesystem/filesystem.hpp> #include <mijin/virtual_filesystem/filesystem.hpp>
namespace sdl_gpu_test namespace sdl_gpu_test