Implemented rotating cube.

This commit is contained in:
Patrick 2024-09-15 14:46:16 +02:00
parent bcdabba916
commit 5f8a4656fe
13 changed files with 379 additions and 7 deletions

BIN
assets/bitmaps/cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

12
assets/meshes/cube.mtl Normal file
View File

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

View File

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

@ -1 +1 @@
Subproject commit fe8f329b3852b0492cee6fc3c7d5ed71cf761c07
Subproject commit c74fbb8798a78227d4f53b508c079eae6245246d

View File

@ -0,0 +1,176 @@
#include "./app.hpp"
#include "../util/mesh.hpp"
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <glm/gtc/matrix_transform.hpp>
#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<Uint32>(mesh.vertices.size());
// create vertex buffer
mVertexBuffer.create(mDevice, {
.usage = {.vertex = true},
.size = static_cast<Uint32>(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<float>(swapchainWidth),
/* height = */ static_cast<float>(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();
}
}

View File

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

View File

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

View File

@ -1,6 +1,8 @@
#include "./application.hpp"
#include <chrono>
#include <mijin/util/variant.hpp>
#include <mijin/virtual_filesystem/relative.hpp>
#include <spdlog/spdlog.h>
@ -48,9 +50,12 @@ void Application::update(const AppUpdateArgs&)
void Application::run(std::span<const char*> args)
{
using clock_t = std::chrono::steady_clock;
init({
.programArgs = args
});
const clock_t::time_point startTime = clock_t::now();
while (mRunning)
{
std::optional<sdlpp::sdl_event_t> event;
@ -61,7 +66,11 @@ void Application::run(std::span<const char*> args)
[](const auto&) {} // default handler
}, *event);
}
update({});
const clock_t::time_point frameTime = clock_t::now();
update({
.secondsSinceStart = std::chrono::duration_cast<std::chrono::duration<float>>(frameTime - startTime).count()
});
}
cleanup({});
}

View File

@ -26,7 +26,7 @@ struct AppCleanupArgs
struct AppUpdateArgs
{
float secondsSinceStart = 0.f;
};
class Application

View File

@ -1,6 +1,7 @@
#include "./0_triangle_with_texcoords/app.hpp"
#include "./1_textured_quad/app.hpp"
#include "./2_textured_cube/app.hpp"
#include <mijin/debug/stacktrace.hpp>
#include <spdlog/spdlog.h>
@ -23,7 +24,8 @@ int main(int argc, char* argv[])
{
// make sure app is destructed before shutting down SDL
// std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TriangleWithTexcoordsApp>();
std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TexturedQuadApp>();
// std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TexturedQuadApp>();
std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TexturedCubeApp>();
app->run(std::span(const_cast<const char**>(argv), argc));
}
catch (std::exception& exception)

View File

@ -433,7 +433,6 @@ static_assert(sizeof(GPUColorTargetDescription) == sizeof(SDL_GPUColorTargetDesc
struct GPUGraphicsPipelineTargetInfo
{
std::span<const GPUColorTargetDescription> 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<const SDL_GPUColorTargetDescription*>(colorTargetDescriptions.data()),
.num_color_targets = static_cast<Uint32>(colorTargetDescriptions.size()),
.has_depth_stencil_target = hasDepthStencilTarget,
.has_depth_stencil_target = depthStencilFormat != GPUTextureFormat::INVALID,
.depth_stencil_format = static_cast<SDL_GPUTextureFormat>(depthStencilFormat)
};
}
@ -843,6 +842,17 @@ public:
return texture;
}
template<typename TData>
void pushVertexUniformData(Uint32 slotIndex, std::span<const TData> data) const noexcept
{
SDL_PushGPUVertexUniformData(
/* command_buffer = */ mHandle,
/* slot_index = */ slotIndex,
/* data = */ data.data(),
/* length = */ data.size_bytes()
);
}
template<typename TData>
void pushFragmentUniformData(Uint32 slotIndex, std::span<const TData> data) const noexcept
{

View File

@ -0,0 +1,75 @@
#include "./mesh.hpp"
#include <tiny_obj_loader.h>
namespace sdl_gpu_test
{
Mesh loadMesh(const mijin::PathReference& path)
{
std::unique_ptr<mijin::Stream> stream;
mijin::throwOnError(path.open(mijin::FileOpenMode::READ, stream));
mijin::IOStreamAdapter adapter(*stream);
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> 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;
}
}

View File

@ -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 <vector>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <mijin/io/stlstream.hpp>
#include <mijin/virtual_filesystem/filesystem.hpp>
namespace sdl_gpu_test
{
struct Vertex
{
glm::vec3 pos;
glm::vec3 normal;
glm::vec2 texcoord;
glm::vec4 color;
};
struct Mesh
{
std::vector<Vertex> 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)