diff --git a/TODO.txt b/TODO.txt index 12301e4..cf690e0 100644 --- a/TODO.txt +++ b/TODO.txt @@ -3,15 +3,13 @@ vulkan/device.cpp: report what is missing when a device fails to be found. Only select physical device 0 to simplify code. -Supported different event 'kinds' (postPhysics, preRender, postRender etc) +Add support for multiple lights. -TODO now: the collision system doesn't use the "isTrigger" bool properly. +Add support for simple shadow mapping. ++ cascaded shadow mapping -Add support for shadows and other complex lighting. Also add post-processing. - -Add AABB colliders, sphere colliders, and mesh colliders. - -Support animations and skinned meshes. +Support animations. ++ skinned meshes / morph targets At some point, add game controller support. Make sure it works well with the InputManager class. @@ -38,3 +36,11 @@ Place all instances of a particular component in contiguous memory: I.e., a scene holds many std::vectors, one for each type of component. These vectors are looped through every frame. This should optimise things by improving the memory layout of the program, significantly reducing cache misses. + +Implemented glTF file loader + +Added a PBR shader with albedo, normal, metallic, roughness, and AO textures + +Added the BVH AABB tree made in Summer to start a much better Collision system. + +The CameraControllerSystem now uses raycasting to enable FPS-style player movement. \ No newline at end of file diff --git a/include/gfx.h b/include/gfx.h index cf30344..41f1c80 100644 --- a/include/gfx.h +++ b/include/gfx.h @@ -23,31 +23,32 @@ struct Image; struct Sampler; enum class MSAALevel { - kOff, - k2X, - k4X, - k8X, - k16X, + kOff, + k2X, + k4X, + k8X, + k16X, }; struct GraphicsSettings { - GraphicsSettings() { - // sane defaults - enable_validation = true; - vsync = true; - // not all GPUs/drivers support immediate present with V-sync enabled - wait_for_present = true; - msaa_level = MSAALevel::kOff; - enable_anisotropy = false; // anisotropic filtering can severely affect performance on intel iGPUs - } + GraphicsSettings() + { + // sane defaults + enable_validation = true; + vsync = true; + // not all GPUs/drivers support immediate present with V-sync enabled + wait_for_present = true; + msaa_level = MSAALevel::kOff; + enable_anisotropy = false; // anisotropic filtering can severely affect performance on intel iGPUs + } - bool enable_validation; - bool vsync; - // idle CPU after render until the frame has been presented - // (no affect with V-sync disabled) - bool wait_for_present; - MSAALevel msaa_level; - bool enable_anisotropy; + bool enable_validation; + bool vsync; + // idle CPU after render until the frame has been presented + // (no affect with V-sync disabled) + bool wait_for_present; + MSAALevel msaa_level; + bool enable_anisotropy; }; enum class ImageFormat { @@ -56,109 +57,115 @@ enum class ImageFormat { }; enum class ShaderType { - kVertex, - kFragment, + kVertex, + kFragment, }; enum class BufferType { - kVertex, - kIndex, - kUniform, + kVertex, + kIndex, + kUniform, }; enum class Primitive { - kPoints, - kLines, - kLineStrip, - kTriangles, - kTriangleStrip, + kPoints, + kLines, + kLineStrip, + kTriangles, + kTriangleStrip, }; -enum class CullMode { - kCullNone, - kCullFront, - kCullBack, - kCullFrontAndBack -}; +enum class CullMode { kCullNone, kCullFront, kCullBack, kCullFrontAndBack }; enum class VertexAttribFormat { kFloat2, kFloat3, kFloat4 }; enum class Filter : int { - kLinear, - kNearest, + kLinear, + kNearest, +}; + +enum class WrapMode : int { + kRepeat, + kMirroredRepeat, + kClampToEdge, }; enum class DescriptorType { - kUniformBuffer, - kCombinedImageSampler, + kUniformBuffer, + kCombinedImageSampler, }; namespace ShaderStageFlags { enum Bits : uint32_t { - kVertex = 1 << 0, - kFragment = 1 << 1, + kVertex = 1 << 0, + kFragment = 1 << 1, }; typedef std::underlying_type::type Flags; -} // namespace ShaderStageFlags +} // namespace ShaderStageFlags struct VertexAttribDescription { - VertexAttribDescription(uint32_t location, VertexAttribFormat format, - uint32_t offset) - : location(location), format(format), offset(offset) {} - uint32_t location; // the index to use in the shader - VertexAttribFormat format; - uint32_t offset; + VertexAttribDescription(uint32_t location, VertexAttribFormat format, uint32_t offset) : location(location), format(format), offset(offset) {} + uint32_t location; // the index to use in the shader + VertexAttribFormat format; + uint32_t offset; }; struct VertexFormat { - uint32_t stride; - std::vector attribute_descriptions; + uint32_t stride; + std::vector attribute_descriptions; }; struct PipelineInfo { - std::string vert_shader_path; - std::string frag_shader_path; - VertexFormat vertex_format; - bool alpha_blending; - bool backface_culling; - bool write_z; - bool line_primitives; // false for triangles, true for lines - std::vector descriptor_set_layouts; + std::string vert_shader_path; + std::string frag_shader_path; + VertexFormat vertex_format; + bool alpha_blending; + bool backface_culling; + bool write_z; + bool line_primitives; // false for triangles, true for lines + std::vector descriptor_set_layouts; }; struct DescriptorSetLayoutBinding { - DescriptorType descriptor_type = DescriptorType::kUniformBuffer; - ShaderStageFlags::Flags stage_flags = 0; + DescriptorType descriptor_type = DescriptorType::kUniformBuffer; + ShaderStageFlags::Flags stage_flags = 0; }; struct SamplerInfo { - Filter minify = gfx::Filter::kLinear; - Filter magnify = gfx::Filter::kLinear; - Filter mipmap = gfx::Filter::kLinear; - bool anisotropic_filtering = true; // this can be force disabled by a global setting + Filter minify = gfx::Filter::kLinear; + Filter magnify = gfx::Filter::kLinear; + Filter mipmap = gfx::Filter::kLinear; + WrapMode wrap_u = gfx::WrapMode::kRepeat; + WrapMode wrap_v = gfx::WrapMode::kRepeat; + WrapMode wrap_w = gfx::WrapMode::kRepeat; // only useful for cubemaps AFAIK + bool anisotropic_filtering = true; // this can be force disabled by a global setting - bool operator==(const SamplerInfo&) const = default; + bool operator==(const SamplerInfo&) const = default; }; -} // namespace gfx -} // namespace engine +} // namespace gfx +} // namespace engine namespace std { template <> struct hash { - std::size_t operator()(const engine::gfx::SamplerInfo& k) const { - using std::hash; + std::size_t operator()(const engine::gfx::SamplerInfo& k) const + { + using std::hash; - size_t h1 = hash()(static_cast(k.minify)); - size_t h2 = hash()(static_cast(k.magnify)); - size_t h3 = hash()(static_cast(k.mipmap)); - size_t h4 = hash()(k.anisotropic_filtering); + size_t h1 = hash()(static_cast(k.minify)); + size_t h2 = hash()(static_cast(k.magnify)); + size_t h3 = hash()(static_cast(k.mipmap)); + size_t h4 = hash()(static_cast(k.wrap_u)); + size_t h5 = hash()(static_cast(k.wrap_v)); + size_t h6 = hash()(static_cast(k.wrap_w)); + size_t h7 = hash()(k.anisotropic_filtering); - return ((h1 & 0xFF) << 24) | ((h2 & 0xFF) << 16) | ((h3 & 0xFF) << 8) | - ((h4 & 0xFF) << 0); - } + return ((h1 & 0xFF) << 48) | ((h2 & 0xFF) << 40) | ((h3 & 0xFF) << 32) | ((h4 & 0xFF) << 24) | ((h5 & 0xFF) << 16) | ((h6 & 0xFF) << 8) | + ((h7 & 0xFF) << 0); + } }; -} // namespace std +} // namespace std #endif \ No newline at end of file diff --git a/include/gfx_device.h b/include/gfx_device.h index c4baee4..4fc41d6 100644 --- a/include/gfx_device.h +++ b/include/gfx_device.h @@ -104,6 +104,8 @@ class GFXDevice { gfx::Image* CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const void* image_data); + gfx::Image* CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const std::array& image_data); + void DestroyImage(const gfx::Image* image); const gfx::Sampler* CreateSampler(const gfx::SamplerInfo& info); diff --git a/include/renderer.h b/include/renderer.h index 6b3945c..33237a9 100644 --- a/include/renderer.h +++ b/include/renderer.h @@ -58,7 +58,7 @@ class Renderer : private ApplicationComponent { struct CameraSettings { float vertical_fov_radians = glm::radians(70.0f); - float clip_near = 0.5f; + float clip_near = 0.1f; float clip_far = 1000.0f; } camera_settings_; @@ -101,6 +101,11 @@ class Renderer : private ApplicationComponent { // shader will take 2 clip space xyzw coords as push constants to define the line } debug_rendering_things_{}; + gfx::Image* skybox_cubemap = nullptr; + const gfx::Sampler* skybox_sampler = nullptr; + const gfx::Pipeline* skybox_pipeline = nullptr; + const gfx::Buffer* skybox_buffer = nullptr; + void DrawRenderList(gfx::DrawBuffer* draw_buffer, const RenderList& render_list); }; diff --git a/include/resources/material.h b/include/resources/material.h index 5336509..2520cff 100644 --- a/include/resources/material.h +++ b/include/resources/material.h @@ -19,8 +19,7 @@ class Material { void SetAlbedoTexture(std::shared_ptr texture); void SetNormalTexture(std::shared_ptr texture); - void SetOcclusionTexture(std::shared_ptr texture); - void SetMetallicRoughnessTexture(std::shared_ptr texture); + void SetOcclusionRoughnessMetallicTexture(std::shared_ptr texture); const gfx::DescriptorSet* GetDescriptorSet() { return material_set_; } Shader* GetShader() { return shader_.get(); } @@ -29,8 +28,7 @@ class Material { const std::shared_ptr shader_; std::shared_ptr texture_albedo_; std::shared_ptr texture_normal_; - std::shared_ptr texture_occlusion_; - std::shared_ptr texture_metallic_roughness_; + std::shared_ptr texture_occlusion_roughness_metallic_; const gfx::DescriptorSet* material_set_ = nullptr; diff --git a/res/engine/shaders/fancy.frag b/res/engine/shaders/fancy.frag index 2583306..abea1ac 100644 --- a/res/engine/shaders/fancy.frag +++ b/res/engine/shaders/fancy.frag @@ -3,15 +3,19 @@ #define PI 3.1415926535897932384626433832795 #define PI_INV 0.31830988618379067153776752674503 +layout(set = 0, binding = 1) uniform samplerCube globalSetSkybox; + layout(set = 2, binding = 0) uniform sampler2D materialSetAlbedoSampler; layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler; -layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionSampler; -layout(set = 2, binding = 3) uniform sampler2D materialSetMetallicRoughnessSampler; +layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionRoughnessMetallic; layout(location = 0) in vec2 fragUV; layout(location = 1) in vec3 fragPosTangentSpace; layout(location = 2) in vec3 fragViewPosTangentSpace; layout(location = 3) in vec3 fragLightPosTangentSpace; +layout(location = 4) in vec3 fragNormWorldSpace; +layout(location = 5) in vec3 fragViewPosWorldSpace; +layout(location = 6) in vec3 fragPosWorldSpace; layout(location = 0) out vec4 outColor; @@ -28,21 +32,24 @@ float GGXDist(float alpha_2, float N_dot_H) { void main() { - const vec3 metallic_roughness = vec3(texture(materialSetMetallicRoughnessSampler, fragUV)); - const float metallic = metallic_roughness.b; - const float roughness = metallic_roughness.g; // roughness of zero is completely black? + const vec3 occlusion_roughness_metallic = vec3(texture(materialSetOcclusionRoughnessMetallic, fragUV)); + const float ao = occlusion_roughness_metallic.r; + const float roughness = occlusion_roughness_metallic.g; + const float metallic = occlusion_roughness_metallic.b; + + const vec3 I = normalize(fragPosWorldSpace - fragViewPosWorldSpace); + const vec3 R = reflect(I, fragNormWorldSpace); + const float roughness_2 = roughness * roughness; const vec3 light_colour = vec3(1.0, 1.0, 1.0) * 2.4; const vec3 emission = vec3(0.0, 0.0, 0.0); - const float ao = texture(materialSetOcclusionSampler, fragUV).r; - const vec3 albedo = vec3(texture(materialSetAlbedoSampler, fragUV)); const vec3 N = GetNormal(); const vec3 V = normalize(fragViewPosTangentSpace - fragPosTangentSpace); - const vec3 L = normalize(fragLightPosTangentSpace - fragPosTangentSpace); + const vec3 L = normalize(fragLightPosTangentSpace); //const vec3 L = normalize(vec3(5.0, 0.0, 3.0)); const vec3 H = normalize(V + L); @@ -66,10 +73,13 @@ void main() { const vec3 dielectric_brdf = mix(diffuse_brdf, specular_brdf, 0.04 + (1 - 0.04) * pow(1 - abs(V_dot_H), 5)); const vec3 metal_brdf = specular_brdf * (albedo + (1 - albedo) * pow(1 - V_dot_H, 5.0) ); - + const vec3 brdf = mix(dielectric_brdf, metal_brdf, metallic); - const vec3 lighting = brdf * light_colour * L_dot_N; + vec3 lighting = brdf * light_colour * L_dot_N; + + vec3 ambient_light = vec3(0.09082, 0.13281, 0.18164); + lighting += mix(ambient_light, texture(globalSetSkybox, R).rgb, metallic) * ao * diffuse_brdf; // this is NOT physically-based, it just looks cool outColor = vec4(min(emission + lighting, 1.0), 1.0); } diff --git a/res/engine/shaders/fancy.vert b/res/engine/shaders/fancy.vert index d1c79d1..fcb99d2 100644 --- a/res/engine/shaders/fancy.vert +++ b/res/engine/shaders/fancy.vert @@ -21,6 +21,9 @@ layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragPosTangentSpace; layout(location = 2) out vec3 fragViewPosTangentSpace; layout(location = 3) out vec3 fragLightPosTangentSpace; +layout(location = 4) out vec3 fragNormWorldSpace; +layout(location = 5) out vec3 fragViewPosWorldSpace; +layout(location = 6) out vec3 fragPosWorldSpace; void main() { vec4 worldPosition = constants.model * vec4(inPosition, 1.0); @@ -34,7 +37,11 @@ void main() { fragUV = inUV; fragPosTangentSpace = worldToTangentSpace * vec3(worldPosition); fragViewPosTangentSpace = worldToTangentSpace * vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0)); - fragLightPosTangentSpace = worldToTangentSpace * vec3(10000.0, 0000.0, 20000.0); + fragLightPosTangentSpace = worldToTangentSpace * vec3(-0.4278,0.7923,0.43502); + + fragNormWorldSpace = N; + fragViewPosWorldSpace = vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0)); + fragPosWorldSpace = worldPosition.xyz; gl_Position.y *= -1.0; } diff --git a/res/engine/shaders/skybox.frag b/res/engine/shaders/skybox.frag index f5f448c..480fe4f 100644 --- a/res/engine/shaders/skybox.frag +++ b/res/engine/shaders/skybox.frag @@ -1,14 +1,11 @@ #version 450 -layout(set = 2, binding = 0) uniform sampler2D materialSetAlbedoSampler; -layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler; -layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionSampler; -layout(set = 2, binding = 3) uniform sampler2D materialSetMetallicRoughnessSampler; +layout(set = 0, binding = 1) uniform samplerCube cubeSampler; -layout(location = 0) in vec2 fragUV; +layout(location = 0) in vec3 fragPosition; layout(location = 0) out vec4 outColor; void main() { - outColor = texture(materialSetAlbedoSampler, fragUV); + outColor = texture(cubeSampler, fragPosition); } \ No newline at end of file diff --git a/res/engine/shaders/skybox.vert b/res/engine/shaders/skybox.vert index fd6b003..d8cda9b 100644 --- a/res/engine/shaders/skybox.vert +++ b/res/engine/shaders/skybox.vert @@ -8,20 +8,13 @@ layout(set = 1, binding = 0) uniform FrameSetUniformBuffer { mat4 view; } frameSetUniformBuffer; -layout( push_constant ) uniform Constants { - mat4 model; -} constants; - layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNorm; -layout(location = 2) in vec4 inTangent; -layout(location = 3) in vec2 inUV; -layout(location = 0) out vec2 fragUV; +layout(location = 0) out vec3 fragPosition; void main() { - vec3 position = mat3(frameSetUniformBuffer.view) * vec3(constants.model * vec4(inPosition, 1.0)); - gl_Position = (globalSetUniformBuffer.proj * vec4(position, 0.0)).xyzz; - fragUV = inUV; - gl_Position.y *= -1.0; + fragPosition = inPosition; + gl_Position = (globalSetUniformBuffer.proj * vec4(mat3(frameSetUniformBuffer.view) * inPosition, 0.0)).xyzz; + + gl_Position.y *= -1.0; } diff --git a/res/engine/textures/normal.png b/res/engine/textures/normal.png deleted file mode 100644 index ae5aaee..0000000 Binary files a/res/engine/textures/normal.png and /dev/null differ diff --git a/res/engine/textures/skybox0.jpg b/res/engine/textures/skybox0.jpg new file mode 100644 index 0000000..ed291e1 Binary files /dev/null and b/res/engine/textures/skybox0.jpg differ diff --git a/res/engine/textures/skybox1.jpg b/res/engine/textures/skybox1.jpg new file mode 100644 index 0000000..65f5f4d Binary files /dev/null and b/res/engine/textures/skybox1.jpg differ diff --git a/res/engine/textures/skybox2.jpg b/res/engine/textures/skybox2.jpg new file mode 100644 index 0000000..55db75f Binary files /dev/null and b/res/engine/textures/skybox2.jpg differ diff --git a/res/engine/textures/skybox3.jpg b/res/engine/textures/skybox3.jpg new file mode 100644 index 0000000..8c4fbac Binary files /dev/null and b/res/engine/textures/skybox3.jpg differ diff --git a/res/engine/textures/skybox4.jpg b/res/engine/textures/skybox4.jpg new file mode 100644 index 0000000..d9340b8 Binary files /dev/null and b/res/engine/textures/skybox4.jpg differ diff --git a/res/engine/textures/skybox5.jpg b/res/engine/textures/skybox5.jpg new file mode 100644 index 0000000..090c70a Binary files /dev/null and b/res/engine/textures/skybox5.jpg differ diff --git a/res/engine/textures/white.png b/res/engine/textures/white.png deleted file mode 100644 index b7b6ab4..0000000 Binary files a/res/engine/textures/white.png and /dev/null differ diff --git a/src/application.cpp b/src/application.cpp index 970292e..c688a33 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -120,21 +120,6 @@ Application::Application(const char* appName, const char* appVersion, gfx::Graph std::make_unique(renderer(), GetResourcePath("engine/shaders/fancy.vert"), GetResourcePath("engine/shaders/fancy.frag"), shaderSettings); GetResourceManager()->AddPersistent("builtin.fancy", std::move(fancyShader)); } - { - Shader::VertexParams vertParams{}; - vertParams.has_normal = true; - vertParams.has_tangent = true; - vertParams.has_uv0 = true; - Shader::ShaderSettings shaderSettings{}; - shaderSettings.vertexParams = vertParams; - shaderSettings.alpha_blending = false; - shaderSettings.cull_backface = true; - shaderSettings.write_z = false; - shaderSettings.render_order = 1; - auto skyboxShader = - std::make_unique(renderer(), GetResourcePath("engine/shaders/skybox.vert"), GetResourcePath("engine/shaders/skybox.frag"), shaderSettings); - GetResourceManager()->AddPersistent("builtin.skybox", std::move(skyboxShader)); - } /* default textures */ { @@ -208,7 +193,8 @@ void Application::GameLoop() struct DebugMenuState { bool menu_active = false; - bool show_aabbs = false; + bool show_entity_boxes = false; + bool show_bounding_volumes = false; bool show_info_window = false; } debug_menu_state; @@ -246,7 +232,8 @@ void Application::GameLoop() if (ImGui::Begin("debugMenu", 0)) { ImGui::Text("Test!"); ImGui::Text("FPS: %.3f", std::roundf(avg_fps)); - ImGui::Checkbox("Show AABBs?", &debug_menu_state.show_aabbs); + ImGui::Checkbox("Show entity hitboxes?", &debug_menu_state.show_entity_boxes); + ImGui::Checkbox("Show bounding volumes?", &debug_menu_state.show_bounding_volumes); } ImGui::End(); } @@ -288,7 +275,7 @@ void Application::GameLoop() const RenderList* dynamic_list = nullptr; glm::mat4 camera_transform{1.0f}; if (scene) { - if (debug_menu_state.show_aabbs) { + if (debug_menu_state.show_entity_boxes) { if (CollisionSystem* colsys = scene->GetSystem()) { for (const auto& node : colsys->bvh_) { if (node.type1 == CollisionSystem::BiTreeNode::Type::Entity) { @@ -354,6 +341,72 @@ void Application::GameLoop() } } } + if (debug_menu_state.show_bounding_volumes) { + if (CollisionSystem* colsys = scene->GetSystem()) { + for (const auto& node : colsys->bvh_) { + if (node.type1 == CollisionSystem::BiTreeNode::Type::BoundingVolume) { + const glm::vec3 col = + (node.type1 == CollisionSystem::BiTreeNode::Type::BoundingVolume) ? glm::vec3{ 1.0f, 0.0f, 0.0f } : glm::vec3{ 0.0f, 1.0f, 0.0f }; + Line line1{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, col }; + debug_lines.push_back(line1); + Line line2{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, col }; + debug_lines.push_back(line2); + Line line3{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, col }; + debug_lines.push_back(line3); + Line line4{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, col }; + debug_lines.push_back(line4); + + Line line5{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, col }; + debug_lines.push_back(line5); + Line line6{ glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col }; + debug_lines.push_back(line6); + Line line7{ glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col }; + debug_lines.push_back(line7); + Line line8{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, col }; + debug_lines.push_back(line8); + + Line line9{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col }; + debug_lines.push_back(line9); + Line line10{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col }; + debug_lines.push_back(line10); + Line line11{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col }; + debug_lines.push_back(line11); + Line line12{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col }; + debug_lines.push_back(line12); + } + if (node.type2 == CollisionSystem::BiTreeNode::Type::BoundingVolume) { + const glm::vec3 col = + (node.type2 == CollisionSystem::BiTreeNode::Type::BoundingVolume) ? glm::vec3{ 1.0f, 0.0f, 0.0f } : glm::vec3{ 0.0f, 1.0f, 0.0f }; + Line line1{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, col }; + debug_lines.push_back(line1); + Line line2{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, col }; + debug_lines.push_back(line2); + Line line3{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, col }; + debug_lines.push_back(line3); + Line line4{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, col }; + debug_lines.push_back(line4); + + Line line5{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, col }; + debug_lines.push_back(line5); + Line line6{ glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col }; + debug_lines.push_back(line6); + Line line7{ glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col }; + debug_lines.push_back(line7); + Line line8{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, col }; + debug_lines.push_back(line8); + + Line line9{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col }; + debug_lines.push_back(line9); + Line line10{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col }; + debug_lines.push_back(line10); + Line line11{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col }; + debug_lines.push_back(line11); + Line line12{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col }; + debug_lines.push_back(line12); + } + } + } + } camera_transform = scene->GetComponent(scene->GetEntity("camera"))->world_matrix; auto mesh_render_system = scene->GetSystem(); static_list = mesh_render_system->GetStaticRenderList(); diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index ca2ebf3..de6a6fa 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -173,13 +173,26 @@ static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) throw std::runtime_error("Unknown filter"); } +[[maybe_unused]] static VkSamplerAddressMode getSamplerAddressMode(gfx::WrapMode mode) +{ + switch (mode) { + case gfx::WrapMode::kRepeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case gfx::WrapMode::kMirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case gfx::WrapMode::kClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + } + throw std::runtime_error("Unknown wrap mode"); +} + [[maybe_unused]] static VkSamplerMipmapMode getSamplerMipmapMode(gfx::Filter filter) { switch (filter) { - case gfx::Filter::kLinear: - return VK_SAMPLER_MIPMAP_MODE_LINEAR; - case gfx::Filter::kNearest: - return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case gfx::Filter::kLinear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + case gfx::Filter::kNearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; } throw std::runtime_error("Unknown filter"); } @@ -1377,7 +1390,7 @@ void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer) delete buffer; } -// imageData must have pixel format R8G8B8A8_SRGB +// imageData must have pixel format R8G8B8A8 gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const void* imageData) { assert(imageData != nullptr); @@ -1624,6 +1637,263 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu return out; } +gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const std::array& image_data) +{ + assert(image_data[0] != nullptr); + assert(image_data[1] != nullptr); + assert(image_data[2] != nullptr); + assert(image_data[3] != nullptr); + assert(image_data[4] != nullptr); + assert(image_data[5] != nullptr); + + if (pimpl->FRAMECOUNT != 0) abort(); // TODO. This is annoying + + gfx::Image* out = new gfx::Image{}; + + uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(w, h)))) + 1; + VkFormat imageFormat = converters::getImageFormat(input_format); + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VmaAllocation stagingAllocation = VK_NULL_HANDLE; + const VkDeviceSize stagingBufferSizePerSide = (VkDeviceSize)w * (VkDeviceSize)h * 4; + + /* create staging buffer */ + { + VkBufferCreateInfo stagingBufferInfo{}; + stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + stagingBufferInfo.size = stagingBufferSizePerSide * 6; + stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + stagingBufferInfo.flags = 0; + + VmaAllocationCreateInfo stagingAllocInfo{}; + stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr)); + + void* dataDest; + VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest)); + for (size_t i = 0; i < 6; ++i) { + memcpy(reinterpret_cast(dataDest) + stagingBufferSizePerSide * i, image_data[i], stagingBufferSizePerSide); + } + vmaUnmapMemory(pimpl->allocator, stagingAllocation); + } + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = imageFormat; + imageInfo.extent.width = w; + imageInfo.extent.height = h; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 6; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.flags = 0; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocCreateInfo.priority = 0.5f; + + VKCHECK(vmaCreateImage(pimpl->allocator, &imageInfo, &allocCreateInfo, &out->image, &out->allocation, nullptr)); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = out->image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + viewInfo.format = imageFormat; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 6; + + VKCHECK(vkCreateImageView(pimpl->device.device, &viewInfo, nullptr, &out->view)); + + /* begin command buffer */ + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = pimpl->graphicsCommandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &allocInfo, &commandBuffer)); + + { // record the command buffer + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + // barrier: (all mip levels): UNDEFINED -> TRANSFER_DST_OPTIMAL + // Used for copying staging buffer AND blitting mipmaps + // Must happen before vkCmdCopyBufferToImage performs a TRANSFER_WRITE in + // the COPY stage. + VkImageMemoryBarrier2 beforeCopyBarrier{}; + beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE; + beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + beforeCopyBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + beforeCopyBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + beforeCopyBarrier.image = out->image; + beforeCopyBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + beforeCopyBarrier.subresourceRange.baseMipLevel = 0; + beforeCopyBarrier.subresourceRange.levelCount = mipLevels; + beforeCopyBarrier.subresourceRange.baseArrayLayer = 0; + beforeCopyBarrier.subresourceRange.layerCount = 6; + VkDependencyInfo beforeCopyDependency{}; + beforeCopyDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + beforeCopyDependency.imageMemoryBarrierCount = 1; + beforeCopyDependency.pImageMemoryBarriers = &beforeCopyBarrier; + vkCmdPipelineBarrier2(commandBuffer, &beforeCopyDependency); + + for (int face = 0; face < 6; ++face) { + // copy staging buffer to mipLevel 0 (full res image) + VkBufferImageCopy region{}; + region.bufferOffset = stagingBufferSizePerSide * face; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = face; + region.imageSubresource.layerCount = 1; + region.imageOffset.x = 0; + region.imageOffset.y = 0; + region.imageOffset.z = 0; + region.imageExtent = imageInfo.extent; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + int32_t mipWidth = w; + int32_t mipHeight = h; + for (uint32_t i = 1; i < mipLevels; i++) { + // barrier: (i - 1) TRANSFER_DST_OPTIMAL -> TRANSFER_SRC_OPTIMAL + // Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage. + VkImageMemoryBarrier2 beforeBlitBarrier{}; + beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + beforeBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; + beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; + beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + beforeBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + beforeBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + beforeBlitBarrier.image = out->image; + beforeBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + beforeBlitBarrier.subresourceRange.baseMipLevel = (i - 1); + beforeBlitBarrier.subresourceRange.levelCount = 1; + beforeBlitBarrier.subresourceRange.baseArrayLayer = face; + beforeBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo beforeBlitDependency{}; + beforeBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + beforeBlitDependency.imageMemoryBarrierCount = 1; + beforeBlitDependency.pImageMemoryBarriers = &beforeBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &beforeBlitDependency); + + VkImageBlit blit{}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = face; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = face; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + vkCmdBlitImage(commandBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, + VK_FILTER_LINEAR); + + // barrier: (i - 1) TRANSFER_SRC_OPTIMAL -> SHADER_READ_ONLY_OPTIMALs + // Must happen after usage in the BLIT stage. + // Must happen before SHADER_SAMPLED_READ in the FRAGMENT_SHADER stage + VkImageMemoryBarrier2 afterBlitBarrier{}; + afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; + afterBlitBarrier.srcAccessMask = 0; + afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + afterBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + afterBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + afterBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + afterBlitBarrier.image = out->image; + afterBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + afterBlitBarrier.subresourceRange.baseMipLevel = (i - 1); + afterBlitBarrier.subresourceRange.levelCount = 1; + afterBlitBarrier.subresourceRange.baseArrayLayer = face; + afterBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo afterBlitDependency{}; + afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + afterBlitDependency.imageMemoryBarrierCount = 1; + afterBlitDependency.pImageMemoryBarriers = &afterBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 2) mipHeight /= 2; + } + + // Final mipLevel is never transitioned from TRANSFER_DST_OPTIMAL + // barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL + VkImageMemoryBarrier2 finalBlitBarrier{}; + finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT; + finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + finalBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + finalBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + finalBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + finalBlitBarrier.image = out->image; + finalBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + finalBlitBarrier.subresourceRange.baseMipLevel = (mipLevels - 1); + finalBlitBarrier.subresourceRange.levelCount = 1; + finalBlitBarrier.subresourceRange.baseArrayLayer = face; + finalBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo afterBlitDependency{}; + afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + afterBlitDependency.imageMemoryBarrierCount = 1; + afterBlitDependency.pImageMemoryBarriers = &finalBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); + + } + + VKCHECK(vkEndCommandBuffer(commandBuffer)); + } + + // submit + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VKCHECK(vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, VK_NULL_HANDLE)); + + VKCHECK(vkQueueWaitIdle(pimpl->device.queues.drawQueues[0])); + + vkFreeCommandBuffers(pimpl->device.device, pimpl->graphicsCommandPool, 1, &commandBuffer); + + vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); + + return out; +} + + void GFXDevice::DestroyImage(const gfx::Image* image) { assert(image != nullptr); @@ -1635,15 +1905,15 @@ void GFXDevice::DestroyImage(const gfx::Image* image) const gfx::Sampler* GFXDevice::CreateSampler(const gfx::SamplerInfo& info) { gfx::Sampler* out = new gfx::Sampler{}; - + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = converters::getFilter(info.magnify); samplerInfo.minFilter = converters::getFilter(info.minify); samplerInfo.mipmapMode = converters::getSamplerMipmapMode(info.mipmap); - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeU = converters::getSamplerAddressMode(info.wrap_u); + samplerInfo.addressModeV = converters::getSamplerAddressMode(info.wrap_v); + samplerInfo.addressModeW = converters::getSamplerAddressMode(info.wrap_w); samplerInfo.mipLodBias = 0.0f; samplerInfo.anisotropyEnable = (info.anisotropic_filtering && pimpl->graphicsSettings.enable_anisotropy) ? VK_TRUE : VK_FALSE; samplerInfo.maxAnisotropy = pimpl->device.properties.limits.maxSamplerAnisotropy; diff --git a/src/renderer.cpp b/src/renderer.cpp index 996b8c1..bb926fe 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,6 +1,9 @@ #include "renderer.h" +#include + #include "application_component.h" +#include "util/files.h" #include #include @@ -20,12 +23,16 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati auto& binding0 = globalSetBindings.emplace_back(); binding0.descriptor_type = gfx::DescriptorType::kUniformBuffer; binding0.stage_flags = gfx::ShaderStageFlags::kVertex; + auto& binding1 = globalSetBindings.emplace_back(); + binding1.descriptor_type = gfx::DescriptorType::kCombinedImageSampler; + binding1.stage_flags = gfx::ShaderStageFlags::kFragment; } global_uniform.layout = device_->CreateDescriptorSetLayout(globalSetBindings); global_uniform.set = device_->AllocateDescriptorSet(global_uniform.layout); global_uniform.uniform_buffer_data.data = glm::mat4{1.0f}; global_uniform.uniform_buffer = device_->CreateUniformBuffer(sizeof(global_uniform.uniform_buffer_data), &global_uniform.uniform_buffer_data); device_->UpdateDescriptorUniformBuffer(global_uniform.set, 0, global_uniform.uniform_buffer, 0, sizeof(global_uniform.uniform_buffer_data)); + // binding1 is updated towards the end of the constructor once the skybox texture is loaded std::vector frameSetBindings; { @@ -55,21 +62,134 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati debug_vertex_format.stride = 0; // debug_vertex_format.vertex_attrib_descriptions = empty - gfx::PipelineInfo debug_pipeline_info{}; - debug_pipeline_info.vert_shader_path = GetResourcePath("engine/shaders/debug.vert"); - debug_pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/debug.frag"); - debug_pipeline_info.vertex_format = debug_vertex_format; - debug_pipeline_info.alpha_blending = false; - debug_pipeline_info.backface_culling = false; // probably ignored for line rendering - debug_pipeline_info.write_z = false; // lines don't need the depth buffer - // debug_pipeline_info.descriptor_set_layouts = empty; - debug_pipeline_info.line_primitives = true; + { + gfx::PipelineInfo debug_pipeline_info{}; + debug_pipeline_info.vert_shader_path = GetResourcePath("engine/shaders/debug.vert"); + debug_pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/debug.frag"); + debug_pipeline_info.vertex_format = debug_vertex_format; + debug_pipeline_info.alpha_blending = false; + debug_pipeline_info.backface_culling = false; // probably ignored for line rendering + debug_pipeline_info.write_z = false; // lines don't need the depth buffer + // debug_pipeline_info.descriptor_set_layouts = empty; + debug_pipeline_info.line_primitives = true; - debug_rendering_things_.pipeline = device_->CreatePipeline(debug_pipeline_info); + debug_rendering_things_.pipeline = device_->CreatePipeline(debug_pipeline_info); + } + + // create the skybox cubemap + { + constexpr uint32_t cubemap_w = 2048; + constexpr uint32_t cubemap_h = 2048; + int w{}, h{}; + std::array>, 6> face_unq_ptrs{}; + std::array face_unsafe_ptrs{}; + + for (int face = 0; face < 6; ++face) { + std::string path = std::string("engine/textures/skybox") + std::to_string(face) + std::string(".jpg"); + face_unq_ptrs[face] = util::ReadImageFile(GetResourcePath(path), w, h); + if (cubemap_w != w || cubemap_h != h) throw std::runtime_error("Skybox textures must be 512x512!"); + face_unsafe_ptrs[face] = face_unq_ptrs[face]->data(); + } + + + skybox_cubemap = device_->CreateImageCubemap(cubemap_w, cubemap_h, gfx::ImageFormat::kSRGB, face_unsafe_ptrs); + gfx::SamplerInfo sampler_info{}; + sampler_info.magnify = gfx::Filter::kLinear; + sampler_info.minify = gfx::Filter::kLinear; + sampler_info.mipmap = gfx::Filter::kLinear; + sampler_info.wrap_u = gfx::WrapMode::kClampToEdge; + sampler_info.wrap_v = gfx::WrapMode::kClampToEdge; + sampler_info.wrap_w = gfx::WrapMode::kClampToEdge; + sampler_info.anisotropic_filtering = true; + skybox_sampler = device_->CreateSampler(sampler_info); + } + + // create skybox shader + { + gfx::VertexFormat vertex_format{}; + vertex_format.attribute_descriptions.emplace_back(0, gfx::VertexAttribFormat::kFloat3, 0); + vertex_format.stride = 3 * sizeof(float); + + gfx::PipelineInfo pipeline_info{}; + pipeline_info.vert_shader_path = GetResourcePath("engine/shaders/skybox.vert"); + pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/skybox.frag"); + pipeline_info.vertex_format = vertex_format; + pipeline_info.alpha_blending = false; + pipeline_info.backface_culling = true; + pipeline_info.write_z = false; + pipeline_info.line_primitives = false; + pipeline_info.descriptor_set_layouts.push_back(GetGlobalSetLayout()); + pipeline_info.descriptor_set_layouts.push_back(GetFrameSetLayout()); + + skybox_pipeline = device_->CreatePipeline(pipeline_info); + + device_->UpdateDescriptorCombinedImageSampler(global_uniform.set, 1, skybox_cubemap, skybox_sampler); + } + + // create skybox vertex buffer + { + std::vector v{}; + v.push_back({0.0f, 0.0f, 2.0f}); + v.push_back({0.0f, 0.0f, 0.0f}); + v.push_back({2.0f, 0.0f, 0.0f}); + v.push_back({2.0f, 0.0f, 0.0f}); + v.push_back({2.0f, 0.0f, 2.0f}); + v.push_back({0.0f, 0.0f, 2.0f}); + // back + v.push_back({2.0f, 2.0f, 2.0f }); + v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({0.0f, 2.0f, 0.0f}); + v.push_back({0.0f, 2.0f, 0.0f}); + v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({ 2.0f, 2.0f, 2.0f }); + // left + v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 0.0f}); + v.push_back({0.0f, 0.0f, 0.0f}); + v.push_back({0.0f, 0.0f, 0.0f}); + v.push_back({0.0f, 0.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 2.0f }); + // right + v.push_back({ 2.0f, 0.0f, 2.0f }); + v.push_back({ 2.0f, 0.0f, 0.0f}); + v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({ 2.0f, 2.0f, 2.0f }); + v.push_back({ 2.0f, 0.0f, 2.0f }); + // top + v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 0.0f, 2.0f }); + v.push_back({ 2.0f, 0.0f, 2.0f }); + v.push_back({ 2.0f, 0.0f, 2.0f }); + v.push_back({ 2.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 2.0f }); + // bottom + v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({ 2.0f, 0.0f, 0.0f}); + v.push_back({0.0f, 0.0f, 0.0f}); + v.push_back({0.0f, 0.0f, 0.0f}); + v.push_back({0.0f, 2.0f, 0.0f}); + v.push_back({ 2.0f, 2.0f, 0.0f}); + for (glm::vec3& pos : v) { + pos.x -= 1.0f; + pos.y -= 1.0f; + pos.z -= 1.0f; + } + for (size_t i = 0; i < v.size(); i += 3) { + std::swap(v[i], v[i + 2]); + } + + skybox_buffer = device_->CreateBuffer(gfx::BufferType::kVertex, v.size() * sizeof(glm::vec3), v.data()); + } }; Renderer::~Renderer() { + device_->DestroyBuffer(skybox_buffer); + device_->DestroyPipeline(skybox_pipeline); + device_->DestroySampler(skybox_sampler); + device_->DestroyImage(skybox_cubemap); + device_->DestroyPipeline(debug_rendering_things_.pipeline); for (const auto& [info, sampler] : samplers) { @@ -127,6 +247,15 @@ void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_l glm::vec3 color; }; + // draw skybox + { + device_->CmdBindPipeline(draw_buffer, skybox_pipeline); + device_->CmdBindDescriptorSet(draw_buffer, skybox_pipeline, global_uniform.set, 0); + device_->CmdBindDescriptorSet(draw_buffer, skybox_pipeline, frame_uniform.set, 1); + device_->CmdBindVertexBuffer(draw_buffer, 0, skybox_buffer); + device_->CmdDraw(draw_buffer, 36, 1, 0, 0); + } + // draw debug shit here device_->CmdBindPipeline(draw_buffer, debug_rendering_things_.pipeline); DebugPush push{}; @@ -139,7 +268,7 @@ void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_l } // also make a lil crosshair - push.color = glm::vec3{ 1.0f, 1.0f, 1.0f }; + push.color = glm::vec3{1.0f, 1.0f, 1.0f}; push.pos1 = glm::vec4(-0.05f, 0.0f, 0.0f, 1.0f); push.pos2 = glm::vec4(0.05f, 0.0f, 0.0f, 1.0f); device_->CmdPushConstants(draw_buffer, debug_rendering_things_.pipeline, 0, sizeof(DebugPush), &push); diff --git a/src/resources/material.cpp b/src/resources/material.cpp index a77dc4b..decced2 100644 --- a/src/resources/material.cpp +++ b/src/resources/material.cpp @@ -23,16 +23,10 @@ void Material::SetNormalTexture(std::shared_ptr texture) texture_normal_ = texture; } -void Material::SetOcclusionTexture(std::shared_ptr texture) +void Material::SetOcclusionRoughnessMetallicTexture(std::shared_ptr texture) { renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 2, texture->GetImage(), texture->GetSampler()); - texture_occlusion_ = texture; -} - -void Material::SetMetallicRoughnessTexture(std::shared_ptr texture) -{ - renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 3, texture->GetImage(), texture->GetSampler()); - texture_metallic_roughness_ = texture; + texture_occlusion_roughness_metallic_ = texture; } } // namespace engine diff --git a/src/util/gltf_loader.cpp b/src/util/gltf_loader.cpp index 4d2023d..9271211 100644 --- a/src/util/gltf_loader.cpp +++ b/src/util/gltf_loader.cpp @@ -109,6 +109,14 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) throw std::runtime_error("Failed to load glTF file!"); } + // check for required extensions + if (model.extensionsRequired.empty() == false) { // this loader doesn't support any extensions + for (const auto& ext : model.extensionsRequired) { + LOG_ERROR("Unsupported required extension: {}", ext); + } + throw std::runtime_error("One or more required extensions are unsupported. File: " + path); + } + // test model loading if (model.scenes.size() < 1) { @@ -142,7 +150,10 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) // use missing texture image by default textures.emplace_back(scene.app()->GetResource("builtin.white")); - if (texture.source == -1) continue; + if (texture.source == -1) { + LOG_ERROR("A gltf file specifies a texture with no source."); + continue; + } gfx::SamplerInfo samplerInfo{}; // default to trilinear filtering even if mipmaps are not specified @@ -193,6 +204,9 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) textures.back() = std::make_shared(scene.app()->renderer(), image.image.data(), image.width, image.height, samplerInfo, tex_index_is_base_color[texture_idx]); } + else { + throw std::runtime_error("Texture found in gltf file is unsupported! Make sure texture is 8 bpp. File: " + path); + } ++texture_idx; } @@ -256,20 +270,20 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) materials.back()->SetAlbedoTexture(colour_textures.at(c)); } - // metallic roughness - materials.back()->SetMetallicRoughnessTexture(scene.app()->GetResource("builtin.white")); // default metal = 1.0, rough = 1.0 + // occlusion roughness metallic + materials.back()->SetOcclusionRoughnessMetallicTexture(scene.app()->GetResource("builtin.white")); // default ao = 1.0, rough = 1.0, metal = 1.0 if (material.pbrMetallicRoughness.metallicRoughnessTexture.index != -1) { if (material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord == 0) { - LOG_INFO("Setting metallic roughness texture!"); - materials.back()->SetMetallicRoughnessTexture(textures.at(material.pbrMetallicRoughness.metallicRoughnessTexture.index)); + LOG_INFO("Setting occlusion roughness metallic texture!"); + materials.back()->SetOcclusionRoughnessMetallicTexture(textures.at(material.pbrMetallicRoughness.metallicRoughnessTexture.index)); } else { LOG_WARN("Material {} metallic roughness texture specifies a UV channel other than zero which is unsupported.", material.name); } } else { - LOG_INFO("Creating a metallic-roughness texture..."); - const std::vector mr_values{1.0f, material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.metallicFactor, 1.0f}; + LOG_INFO("Creating occlusion roughness metallic texture..."); + const std::vector mr_values{1.0f /* no AO */, material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.metallicFactor, 1.0f}; Color mr(mr_values); if (metal_rough_textures.contains(mr) == false) { const uint8_t pixel[4] = {mr.r, mr.g, mr.b, mr.a}; @@ -280,15 +294,16 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) samplerInfo.anisotropic_filtering = false; metal_rough_textures.emplace(std::make_pair(mr, std::make_shared(scene.app()->renderer(), pixel, 1, 1, samplerInfo, false))); } - materials.back()->SetMetallicRoughnessTexture(metal_rough_textures.at(mr)); + materials.back()->SetOcclusionRoughnessMetallicTexture(metal_rough_textures.at(mr)); } // occlusion texture - materials.back()->SetOcclusionTexture(scene.app()->GetResource("builtin.white")); // R=255 means no AO so white will work + // if occlusion texture was same as metallic-roughness texture, this will already have been applied if (material.occlusionTexture.index != -1) { if (material.occlusionTexture.texCoord == 0) { - LOG_INFO("Setting occlusion texture!"); - materials.back()->SetOcclusionTexture(textures.at(material.occlusionTexture.index)); + if (material.occlusionTexture.index != material.pbrMetallicRoughness.metallicRoughnessTexture.index) { + throw std::runtime_error(std::string("Material ") + material.name + std::string(" has an ambient occlusion texture different to the metal-rough texture.")); + } } else { LOG_WARN("Material {} occlusion texture specifies a UV channel other than zero which is unsupported.", material.name); diff --git a/src/vulkan/device.cpp b/src/vulkan/device.cpp index 18680bf..6f17959 100644 --- a/src/vulkan/device.cpp +++ b/src/vulkan/device.cpp @@ -11,443 +11,440 @@ namespace engine { - static bool checkQueueFamilySupportsPresent(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t familyIndex) - { - VkBool32 supportsPresent; - VkResult res; - res = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, static_cast(familyIndex), surface, &supportsPresent); - if (res != VK_SUCCESS) throw std::runtime_error("Failed to check for queue family present support!"); - return supportsPresent; - } - - /* chooses a device, creates it, gets its function pointers, and retrieves queues */ - Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface) - { - Device d{}; - - // enumerate physical devices - uint32_t physDeviceCount = 0; - VkResult res; - res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, nullptr); - assert(res == VK_SUCCESS); - if (physDeviceCount == 0) { - throw std::runtime_error("No GPU found with vulkan support!"); - } - std::vector physicalDevices(physDeviceCount); - res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, physicalDevices.data()); - assert(res == VK_SUCCESS); - - std::vector availableExtensions{}; - - for (VkPhysicalDevice physDev : physicalDevices) { - - // first, check extension support - availableExtensions.clear(); - uint32_t extensionCount; - res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, nullptr); - assert(res == VK_SUCCESS); - availableExtensions.resize(extensionCount); - res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, availableExtensions.data()); - assert(res == VK_SUCCESS); - - bool foundRequiredExtensions = true; - for (const char* extToFind : requirements.requiredExtensions) { - bool extFound = false; - for (const auto& ext : availableExtensions) { - if (strcmp(extToFind, ext.extensionName) == 0) { - extFound = true; - break; - } - } - if (!extFound) { - foundRequiredExtensions = false; - break; - } - } - if (!foundRequiredExtensions) continue; // NEXT! - - VkPhysicalDeviceProperties devProps; - vkGetPhysicalDeviceProperties(physDev, &devProps); - - // check that the device supports vulkan 1.3 - if (devProps.apiVersion < VK_API_VERSION_1_3) { - continue; - } - - /* check features */ - VkPhysicalDeviceMemoryPriorityFeaturesEXT memoryPriorityFeatures{}; - memoryPriorityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT; - VkPhysicalDeviceSynchronization2Features synchronization2Features{}; - synchronization2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; - synchronization2Features.pNext = &memoryPriorityFeatures; - VkPhysicalDeviceFeatures2 devFeatures{}; - devFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - devFeatures.pNext = &synchronization2Features; - vkGetPhysicalDeviceFeatures2(physDev, &devFeatures); - { - if (requirements.requiredFeatures.robustBufferAccess) - if (devFeatures.features.robustBufferAccess == VK_FALSE) continue; - if (requirements.requiredFeatures.fullDrawIndexUint32) - if (devFeatures.features.fullDrawIndexUint32 == VK_FALSE) continue; - if (requirements.requiredFeatures.imageCubeArray == VK_TRUE) - if (devFeatures.features.imageCubeArray == VK_FALSE) continue; - if (requirements.requiredFeatures.independentBlend == VK_TRUE) - if (devFeatures.features.independentBlend == VK_FALSE) continue; - if (requirements.requiredFeatures.geometryShader == VK_TRUE) - if (devFeatures.features.geometryShader == VK_FALSE) continue; - if (requirements.requiredFeatures.tessellationShader == VK_TRUE) - if (devFeatures.features.tessellationShader == VK_FALSE) continue; - if (requirements.requiredFeatures.sampleRateShading == VK_TRUE) - if (devFeatures.features.sampleRateShading == VK_FALSE) continue; - if (requirements.requiredFeatures.dualSrcBlend == VK_TRUE) - if (devFeatures.features.dualSrcBlend == VK_FALSE) continue; - if (requirements.requiredFeatures.logicOp == VK_TRUE) - if (devFeatures.features.logicOp == VK_FALSE) continue; - if (requirements.requiredFeatures.multiDrawIndirect == VK_TRUE) - if (devFeatures.features.multiDrawIndirect == VK_FALSE) continue; - if (requirements.requiredFeatures.drawIndirectFirstInstance == VK_TRUE) - if (devFeatures.features.drawIndirectFirstInstance == VK_FALSE) continue; - if (requirements.requiredFeatures.depthClamp == VK_TRUE) - if (devFeatures.features.depthClamp == VK_FALSE) continue; - if (requirements.requiredFeatures.depthBiasClamp == VK_TRUE) - if (devFeatures.features.depthBiasClamp == VK_FALSE) continue; - if (requirements.requiredFeatures.fillModeNonSolid == VK_TRUE) - if (devFeatures.features.fillModeNonSolid == VK_FALSE) continue; - if (requirements.requiredFeatures.depthBounds == VK_TRUE) - if (devFeatures.features.depthBounds == VK_FALSE) continue; - if (requirements.requiredFeatures.wideLines == VK_TRUE) - if (devFeatures.features.wideLines == VK_FALSE) continue; - if (requirements.requiredFeatures.largePoints == VK_TRUE) - if (devFeatures.features.largePoints == VK_FALSE) continue; - if (requirements.requiredFeatures.alphaToOne == VK_TRUE) - if (devFeatures.features.alphaToOne == VK_FALSE) continue; - if (requirements.requiredFeatures.multiViewport == VK_TRUE) - if (devFeatures.features.multiViewport == VK_FALSE) continue; - if (requirements.requiredFeatures.samplerAnisotropy == VK_TRUE) - if (devFeatures.features.samplerAnisotropy == VK_FALSE) continue; - if (requirements.requiredFeatures.textureCompressionETC2 == VK_TRUE) - if (devFeatures.features.textureCompressionETC2 == VK_FALSE) continue; - if (requirements.requiredFeatures.textureCompressionASTC_LDR == VK_TRUE) - if (devFeatures.features.textureCompressionASTC_LDR == VK_FALSE) continue; - if (requirements.requiredFeatures.textureCompressionBC == VK_TRUE) - if (devFeatures.features.textureCompressionBC == VK_FALSE) continue; - if (requirements.requiredFeatures.occlusionQueryPrecise == VK_TRUE) - if (devFeatures.features.occlusionQueryPrecise == VK_FALSE) continue; - if (requirements.requiredFeatures.pipelineStatisticsQuery == VK_TRUE) - if (devFeatures.features.pipelineStatisticsQuery == VK_FALSE) continue; - if (requirements.requiredFeatures.vertexPipelineStoresAndAtomics == VK_TRUE) - if (devFeatures.features.vertexPipelineStoresAndAtomics == VK_FALSE) continue; - if (requirements.requiredFeatures.fragmentStoresAndAtomics == VK_TRUE) - if (devFeatures.features.fragmentStoresAndAtomics == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE) - if (devFeatures.features.shaderTessellationAndGeometryPointSize == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderImageGatherExtended == VK_TRUE) - if (devFeatures.features.shaderImageGatherExtended == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageImageExtendedFormats == VK_TRUE) - if (devFeatures.features.shaderStorageImageExtendedFormats == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageImageMultisample == VK_TRUE) - if (devFeatures.features.shaderStorageImageMultisample == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE) - if (devFeatures.features.shaderStorageImageReadWithoutFormat == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE) - if (devFeatures.features.shaderStorageImageWriteWithoutFormat == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderUniformBufferArrayDynamicIndexing == VK_TRUE) - if (devFeatures.features.shaderUniformBufferArrayDynamicIndexing == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderSampledImageArrayDynamicIndexing == VK_TRUE) - if (devFeatures.features.shaderSampledImageArrayDynamicIndexing == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageBufferArrayDynamicIndexing == VK_TRUE) - if (devFeatures.features.shaderStorageBufferArrayDynamicIndexing == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderStorageImageArrayDynamicIndexing == VK_TRUE) - if (devFeatures.features.shaderStorageImageArrayDynamicIndexing == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderClipDistance == VK_TRUE) - if (devFeatures.features.shaderClipDistance == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderCullDistance == VK_TRUE) - if (devFeatures.features.shaderCullDistance == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderFloat64 == VK_TRUE) - if (devFeatures.features.shaderFloat64 == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderInt64 == VK_TRUE) - if (devFeatures.features.shaderInt64 == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderInt16 == VK_TRUE) - if (devFeatures.features.shaderInt16 == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderResourceResidency == VK_TRUE) - if (devFeatures.features.shaderResourceResidency == VK_FALSE) continue; - if (requirements.requiredFeatures.shaderResourceMinLod == VK_TRUE) - if (devFeatures.features.shaderResourceMinLod == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseBinding == VK_TRUE) - if (devFeatures.features.sparseBinding == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidencyBuffer == VK_TRUE) - if (devFeatures.features.sparseResidencyBuffer == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidencyImage2D == VK_TRUE) - if (devFeatures.features.sparseResidencyImage2D == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidencyImage3D == VK_TRUE) - if (devFeatures.features.sparseResidencyImage3D == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidency2Samples == VK_TRUE) - if (devFeatures.features.sparseResidency2Samples == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidency4Samples == VK_TRUE) - if (devFeatures.features.sparseResidency4Samples == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidency8Samples == VK_TRUE) - if (devFeatures.features.sparseResidency8Samples == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidency16Samples == VK_TRUE) - if (devFeatures.features.sparseResidency16Samples == VK_FALSE) continue; - if (requirements.requiredFeatures.sparseResidencyAliased == VK_TRUE) - if (devFeatures.features.sparseResidencyAliased == VK_FALSE) continue; - if (requirements.requiredFeatures.variableMultisampleRate == VK_TRUE) - if (devFeatures.features.variableMultisampleRate == VK_FALSE) continue; - if (requirements.requiredFeatures.inheritedQueries == VK_TRUE) - if (devFeatures.features.inheritedQueries == VK_FALSE) continue; - - /* ensure synchronization 2 is found */ - if (synchronization2Features.synchronization2 == VK_FALSE) continue; - - /* check the memory priority extension was even requested */ - bool memoryPriorityRequired = false; - for (const char* ext : requirements.requiredExtensions) { - if (strcmp(ext, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { - memoryPriorityRequired = true; - break; - } - } - if (memoryPriorityRequired) { - if (memoryPriorityFeatures.memoryPriority == VK_FALSE) { - throw std::runtime_error("Required device feature 'memoryPriority' not found, but extension was"); - } else { - d.memoryPriorityFeature = true; - } - } else { - // see if memoryPriority was optionally requested */ - for (const char* optExt : requirements.optionalExtensions) { - if (strcmp(optExt, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { - for (const VkExtensionProperties& extAvail : availableExtensions) { - if (strcmp(extAvail.extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { - if (memoryPriorityFeatures.memoryPriority == VK_TRUE) { - d.memoryPriorityFeature = true; - } else { - throw std::runtime_error("Optional device extension 'VK_EXT_memory_priority' found, but feature wasn't"); - } - break; // | - } // | - } // V - // <-------------------- - break; // | - } // | - } // V - // <-------------------- - } - } - - bool formatsSupported = true; - for (const FormatRequirements& formatRequirements : requirements.formats) { - VkFormatFeatureFlags requiredLinearFlags = formatRequirements.properties.linearTilingFeatures; - VkFormatFeatureFlags requiredOptimalFlags = formatRequirements.properties.optimalTilingFeatures; - VkFormatFeatureFlags requiredBufferFlags = formatRequirements.properties.bufferFeatures; - VkFormatProperties deviceFormatProperties{}; - vkGetPhysicalDeviceFormatProperties(physDev, formatRequirements.format, &deviceFormatProperties); - if ((deviceFormatProperties.linearTilingFeatures & requiredLinearFlags) != requiredLinearFlags || - (deviceFormatProperties.optimalTilingFeatures & requiredOptimalFlags) != requiredOptimalFlags || - (deviceFormatProperties.bufferFeatures & requiredBufferFlags) != requiredBufferFlags) { - formatsSupported = false; - break; - } - } - if (formatsSupported == false) continue; - - /* USE THIS PHYSICAL DEVICE */ - d.physicalDevice = physDev; - d.properties = devProps; - d.features = requirements.requiredFeatures; // to be safe, only advertise requested features - //deviceInfo->features = devFeatures; - - break; - } - - if (d.physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("No suitable Vulkan physical device found"); - } - - /* queue families */ - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, nullptr); - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, queueFamilies.data()); - - // find a graphics/present family - uint32_t graphicsFamily = UINT32_MAX; - for (size_t i = 0; i < queueFamilies.size(); i++) { - VkQueueFamilyProperties p = queueFamilies[i]; - - if (p.queueCount < 2) continue; // ideally have one queue for presenting and at least one other for rendering - - if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { - graphicsFamily = static_cast(i); - break; - } - } - } - if (graphicsFamily == UINT32_MAX) { - for (size_t i = 0; i < queueFamilies.size(); i++) { - VkQueueFamilyProperties p = queueFamilies[i]; - if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { - graphicsFamily = static_cast(i); - } - } - } - if (graphicsFamily == UINT32_MAX) { - throw std::runtime_error("Failed to find a graphics/present family!"); - } - LOG_WARN("Failed to find ideal graphics/present queue family! Falling back to family #{}.", graphicsFamily); - } - - // find a transfer queue family (image layout transitions, buffer upload) - uint32_t transferFamily = UINT32_MAX; - // prefer a dedicated transfer queue family - for (size_t i = 0; i < queueFamilies.size(); i++) { - VkQueueFamilyProperties p = queueFamilies[i]; - if (((p.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) && - ((p.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) && - ((p.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) { - transferFamily = static_cast(i); - break; - } - } - if (transferFamily == UINT32_MAX) { - transferFamily = graphicsFamily; - LOG_WARN("Failed to find a dedicated transfer queue family! Falling back to graphics family."); - } - - // queue priorities - std::vector graphicsQueuePriorities(queueFamilies[graphicsFamily].queueCount); - std::fill(graphicsQueuePriorities.begin(), graphicsQueuePriorities.end(), 1.0f); - std::vector transferQueuePriorities(queueFamilies[transferFamily].queueCount); - std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f); - - std::vector queueCreateInfos{}; - queueCreateInfos.push_back(VkDeviceQueueCreateInfo{ - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueFamilyIndex = graphicsFamily, - .queueCount = queueFamilies[graphicsFamily].queueCount, - .pQueuePriorities = graphicsQueuePriorities.data() - }); - - if (transferFamily != graphicsFamily) { - queueCreateInfos.push_back(VkDeviceQueueCreateInfo{ - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueFamilyIndex = transferFamily, - .queueCount = queueFamilies[transferFamily].queueCount, - .pQueuePriorities = transferQueuePriorities.data() - }); - } - - /* set enabled features */ - VkPhysicalDeviceMemoryPriorityFeaturesEXT memoryPriorityFeatures{}; - memoryPriorityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT; - memoryPriorityFeatures.memoryPriority = d.memoryPriorityFeature ? VK_TRUE : VK_FALSE; - VkPhysicalDeviceSynchronization2Features synchronization2Features{}; - synchronization2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; - synchronization2Features.pNext = &memoryPriorityFeatures; - synchronization2Features.synchronization2 = VK_TRUE; - VkPhysicalDeviceFeatures2 featuresToEnable{}; - featuresToEnable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - featuresToEnable.pNext = &synchronization2Features; - featuresToEnable.features = requirements.requiredFeatures; - - /* get list of extensions to enable */ - std::vector extensionsToEnable{}; - for (const VkExtensionProperties& availableExt : availableExtensions) { - if ( std::find(requirements.optionalExtensions.begin(), requirements.optionalExtensions.end(), - std::string(availableExt.extensionName)) != requirements.optionalExtensions.end()) { - extensionsToEnable.push_back(availableExt.extensionName); - } - } - extensionsToEnable.insert(extensionsToEnable.end(), requirements.requiredExtensions.begin(), requirements.requiredExtensions.end()); - - /* create device now */ - VkDeviceCreateInfo deviceCreateInfo{ - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = &featuresToEnable, - .flags = 0, - .queueCreateInfoCount = static_cast(queueCreateInfos.size()), - .pQueueCreateInfos = queueCreateInfos.data(), - .enabledLayerCount = 0, // deprecated and ignored - .ppEnabledLayerNames = nullptr, // deprecated and ignored - .enabledExtensionCount = static_cast(extensionsToEnable.size()), - .ppEnabledExtensionNames = extensionsToEnable.data(), - .pEnabledFeatures = nullptr, - }; - - res = vkCreateDevice(d.physicalDevice, &deviceCreateInfo, nullptr, &d.device); - if (res != VK_SUCCESS) { - throw std::runtime_error("Unable to create Vulkan logical device, error code: " + std::to_string(res)); - } - - volkLoadDevice(d.device); - - /* get list of extensions enabled */ - d.enabledExtensions.clear(); - for (const char* ext : extensionsToEnable) { - // must be copied into std::strings - d.enabledExtensions.emplace_back(ext); - } - - if (transferFamily != graphicsFamily) { - vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); - if (queueFamilies[graphicsFamily].queueCount >= 2) { - d.queues.drawQueues.resize((size_t)queueFamilies[graphicsFamily].queueCount - 1); - for (uint32_t i = 0; i < d.queues.drawQueues.size(); i++) { - vkGetDeviceQueue(d.device, graphicsFamily, i + 1, &d.queues.drawQueues[i]); - } - } else { - d.queues.drawQueues.resize(1); - d.queues.drawQueues[0] = d.queues.presentQueue; - } - d.queues.transferQueues.resize(queueFamilies[transferFamily].queueCount); - for (uint32_t i = 0; i < d.queues.transferQueues.size(); i++) { - vkGetDeviceQueue(d.device, transferFamily, i, &d.queues.transferQueues[i]); - } - } else { - // same graphics family for graphics/present and transfer - uint32_t queueCount = queueFamilies[graphicsFamily].queueCount; - vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); - if (queueCount >= 2) { - d.queues.transferQueues.resize(1); - vkGetDeviceQueue(d.device, graphicsFamily, 1, &d.queues.transferQueues[0]); - // use the remaining queues for drawing - if (queueCount >= 3) { - d.queues.drawQueues.resize((size_t)queueCount - 2); - for (uint32_t i = 0; i < queueCount - 2; i++) { - vkGetDeviceQueue(d.device, graphicsFamily, i + 2, &d.queues.drawQueues[i]); - } - } else { - // 2 queues available - // present and drawing share a queue - // transfer gets its own queue - d.queues.drawQueues.resize(1); - d.queues.drawQueues[0] = d.queues.presentQueue; - } - } else { - // only 1 queue available :( - d.queues.transferQueues.resize(1); - d.queues.transferQueues[0] = d.queues.presentQueue; - d.queues.drawQueues.resize(1); - d.queues.drawQueues[0] = d.queues.presentQueue; - } - } - - d.queues.presentAndDrawQueueFamily = graphicsFamily; - d.queues.transferQueueFamily = transferFamily; - - return d; - - } - - void destroyDevice(Device device) - { - vkDestroyDevice(device.device, nullptr); - } - +static bool checkQueueFamilySupportsPresent(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t familyIndex) +{ + VkBool32 supportsPresent; + VkResult res; + res = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, static_cast(familyIndex), surface, &supportsPresent); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to check for queue family present support!"); + return supportsPresent; } + +/* chooses a device, creates it, gets its function pointers, and retrieves queues */ +Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface) +{ + Device d{}; + + // enumerate physical devices + uint32_t physDeviceCount = 0; + VkResult res; + res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, nullptr); + assert(res == VK_SUCCESS); + if (physDeviceCount == 0) { + throw std::runtime_error("No GPU found with vulkan support!"); + } + std::vector physicalDevices(physDeviceCount); + res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, physicalDevices.data()); + assert(res == VK_SUCCESS); + + std::vector availableExtensions{}; + + for (VkPhysicalDevice physDev : physicalDevices) { + + // first, check extension support + availableExtensions.clear(); + uint32_t extensionCount; + res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); + availableExtensions.resize(extensionCount); + res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, availableExtensions.data()); + assert(res == VK_SUCCESS); + + bool foundRequiredExtensions = true; + for (const char* extToFind : requirements.requiredExtensions) { + bool extFound = false; + for (const auto& ext : availableExtensions) { + if (strcmp(extToFind, ext.extensionName) == 0) { + extFound = true; + break; + } + } + if (!extFound) { + foundRequiredExtensions = false; + break; + } + } + if (!foundRequiredExtensions) continue; // NEXT! + + VkPhysicalDeviceProperties devProps; + vkGetPhysicalDeviceProperties(physDev, &devProps); + + // check that the device supports vulkan 1.3 + if (devProps.apiVersion < VK_API_VERSION_1_3) { + continue; + } + + /* check features */ + VkPhysicalDeviceMemoryPriorityFeaturesEXT memoryPriorityFeatures{}; + memoryPriorityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT; + VkPhysicalDeviceSynchronization2Features synchronization2Features{}; + synchronization2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; + synchronization2Features.pNext = &memoryPriorityFeatures; + VkPhysicalDeviceFeatures2 devFeatures{}; + devFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + devFeatures.pNext = &synchronization2Features; + vkGetPhysicalDeviceFeatures2(physDev, &devFeatures); + { + if (requirements.requiredFeatures.robustBufferAccess) + if (devFeatures.features.robustBufferAccess == VK_FALSE) continue; + if (requirements.requiredFeatures.fullDrawIndexUint32) + if (devFeatures.features.fullDrawIndexUint32 == VK_FALSE) continue; + if (requirements.requiredFeatures.imageCubeArray == VK_TRUE) + if (devFeatures.features.imageCubeArray == VK_FALSE) continue; + if (requirements.requiredFeatures.independentBlend == VK_TRUE) + if (devFeatures.features.independentBlend == VK_FALSE) continue; + if (requirements.requiredFeatures.geometryShader == VK_TRUE) + if (devFeatures.features.geometryShader == VK_FALSE) continue; + if (requirements.requiredFeatures.tessellationShader == VK_TRUE) + if (devFeatures.features.tessellationShader == VK_FALSE) continue; + if (requirements.requiredFeatures.sampleRateShading == VK_TRUE) + if (devFeatures.features.sampleRateShading == VK_FALSE) continue; + if (requirements.requiredFeatures.dualSrcBlend == VK_TRUE) + if (devFeatures.features.dualSrcBlend == VK_FALSE) continue; + if (requirements.requiredFeatures.logicOp == VK_TRUE) + if (devFeatures.features.logicOp == VK_FALSE) continue; + if (requirements.requiredFeatures.multiDrawIndirect == VK_TRUE) + if (devFeatures.features.multiDrawIndirect == VK_FALSE) continue; + if (requirements.requiredFeatures.drawIndirectFirstInstance == VK_TRUE) + if (devFeatures.features.drawIndirectFirstInstance == VK_FALSE) continue; + if (requirements.requiredFeatures.depthClamp == VK_TRUE) + if (devFeatures.features.depthClamp == VK_FALSE) continue; + if (requirements.requiredFeatures.depthBiasClamp == VK_TRUE) + if (devFeatures.features.depthBiasClamp == VK_FALSE) continue; + if (requirements.requiredFeatures.fillModeNonSolid == VK_TRUE) + if (devFeatures.features.fillModeNonSolid == VK_FALSE) continue; + if (requirements.requiredFeatures.depthBounds == VK_TRUE) + if (devFeatures.features.depthBounds == VK_FALSE) continue; + if (requirements.requiredFeatures.wideLines == VK_TRUE) + if (devFeatures.features.wideLines == VK_FALSE) continue; + if (requirements.requiredFeatures.largePoints == VK_TRUE) + if (devFeatures.features.largePoints == VK_FALSE) continue; + if (requirements.requiredFeatures.alphaToOne == VK_TRUE) + if (devFeatures.features.alphaToOne == VK_FALSE) continue; + if (requirements.requiredFeatures.multiViewport == VK_TRUE) + if (devFeatures.features.multiViewport == VK_FALSE) continue; + if (requirements.requiredFeatures.samplerAnisotropy == VK_TRUE) + if (devFeatures.features.samplerAnisotropy == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionETC2 == VK_TRUE) + if (devFeatures.features.textureCompressionETC2 == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionASTC_LDR == VK_TRUE) + if (devFeatures.features.textureCompressionASTC_LDR == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionBC == VK_TRUE) + if (devFeatures.features.textureCompressionBC == VK_FALSE) continue; + if (requirements.requiredFeatures.occlusionQueryPrecise == VK_TRUE) + if (devFeatures.features.occlusionQueryPrecise == VK_FALSE) continue; + if (requirements.requiredFeatures.pipelineStatisticsQuery == VK_TRUE) + if (devFeatures.features.pipelineStatisticsQuery == VK_FALSE) continue; + if (requirements.requiredFeatures.vertexPipelineStoresAndAtomics == VK_TRUE) + if (devFeatures.features.vertexPipelineStoresAndAtomics == VK_FALSE) continue; + if (requirements.requiredFeatures.fragmentStoresAndAtomics == VK_TRUE) + if (devFeatures.features.fragmentStoresAndAtomics == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE) + if (devFeatures.features.shaderTessellationAndGeometryPointSize == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderImageGatherExtended == VK_TRUE) + if (devFeatures.features.shaderImageGatherExtended == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageExtendedFormats == VK_TRUE) + if (devFeatures.features.shaderStorageImageExtendedFormats == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageMultisample == VK_TRUE) + if (devFeatures.features.shaderStorageImageMultisample == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE) + if (devFeatures.features.shaderStorageImageReadWithoutFormat == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE) + if (devFeatures.features.shaderStorageImageWriteWithoutFormat == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderUniformBufferArrayDynamicIndexing == VK_TRUE) + if (devFeatures.features.shaderUniformBufferArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderSampledImageArrayDynamicIndexing == VK_TRUE) + if (devFeatures.features.shaderSampledImageArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageBufferArrayDynamicIndexing == VK_TRUE) + if (devFeatures.features.shaderStorageBufferArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageArrayDynamicIndexing == VK_TRUE) + if (devFeatures.features.shaderStorageImageArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderClipDistance == VK_TRUE) + if (devFeatures.features.shaderClipDistance == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderCullDistance == VK_TRUE) + if (devFeatures.features.shaderCullDistance == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderFloat64 == VK_TRUE) + if (devFeatures.features.shaderFloat64 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderInt64 == VK_TRUE) + if (devFeatures.features.shaderInt64 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderInt16 == VK_TRUE) + if (devFeatures.features.shaderInt16 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderResourceResidency == VK_TRUE) + if (devFeatures.features.shaderResourceResidency == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderResourceMinLod == VK_TRUE) + if (devFeatures.features.shaderResourceMinLod == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseBinding == VK_TRUE) + if (devFeatures.features.sparseBinding == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyBuffer == VK_TRUE) + if (devFeatures.features.sparseResidencyBuffer == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyImage2D == VK_TRUE) + if (devFeatures.features.sparseResidencyImage2D == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyImage3D == VK_TRUE) + if (devFeatures.features.sparseResidencyImage3D == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency2Samples == VK_TRUE) + if (devFeatures.features.sparseResidency2Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency4Samples == VK_TRUE) + if (devFeatures.features.sparseResidency4Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency8Samples == VK_TRUE) + if (devFeatures.features.sparseResidency8Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency16Samples == VK_TRUE) + if (devFeatures.features.sparseResidency16Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyAliased == VK_TRUE) + if (devFeatures.features.sparseResidencyAliased == VK_FALSE) continue; + if (requirements.requiredFeatures.variableMultisampleRate == VK_TRUE) + if (devFeatures.features.variableMultisampleRate == VK_FALSE) continue; + if (requirements.requiredFeatures.inheritedQueries == VK_TRUE) + if (devFeatures.features.inheritedQueries == VK_FALSE) continue; + + /* ensure synchronization 2 is found */ + if (synchronization2Features.synchronization2 == VK_FALSE) continue; + + /* check the memory priority extension was even requested */ + bool memoryPriorityRequired = false; + for (const char* ext : requirements.requiredExtensions) { + if (strcmp(ext, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { + memoryPriorityRequired = true; + break; + } + } + if (memoryPriorityRequired) { + if (memoryPriorityFeatures.memoryPriority == VK_FALSE) { + throw std::runtime_error("Required device feature 'memoryPriority' not found, but extension was"); + } + else { + d.memoryPriorityFeature = true; + } + } + else { + // see if memoryPriority was optionally requested */ + for (const char* optExt : requirements.optionalExtensions) { + if (strcmp(optExt, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { + for (const VkExtensionProperties& extAvail : availableExtensions) { + if (strcmp(extAvail.extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) { + if (memoryPriorityFeatures.memoryPriority == VK_TRUE) { + d.memoryPriorityFeature = true; + } + else { + throw std::runtime_error("Optional device extension 'VK_EXT_memory_priority' found, but feature wasn't"); + } + break; // | + } // | + } // V + // <--------------- + break; // | + } // | + } // V + // <--------------- + } + } + + bool formatsSupported = true; + for (const FormatRequirements& formatRequirements : requirements.formats) { + VkFormatFeatureFlags requiredLinearFlags = formatRequirements.properties.linearTilingFeatures; + VkFormatFeatureFlags requiredOptimalFlags = formatRequirements.properties.optimalTilingFeatures; + VkFormatFeatureFlags requiredBufferFlags = formatRequirements.properties.bufferFeatures; + VkFormatProperties deviceFormatProperties{}; + vkGetPhysicalDeviceFormatProperties(physDev, formatRequirements.format, &deviceFormatProperties); + if ((deviceFormatProperties.linearTilingFeatures & requiredLinearFlags) != requiredLinearFlags || + (deviceFormatProperties.optimalTilingFeatures & requiredOptimalFlags) != requiredOptimalFlags || + (deviceFormatProperties.bufferFeatures & requiredBufferFlags) != requiredBufferFlags) { + formatsSupported = false; + break; + } + } + if (formatsSupported == false) continue; + + /* USE THIS PHYSICAL DEVICE */ + d.physicalDevice = physDev; + d.properties = devProps; + d.features = requirements.requiredFeatures; // to be safe, only advertise requested features + // deviceInfo->features = devFeatures; + + break; + } + + if (d.physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("No suitable Vulkan physical device found"); + } + + /* queue families */ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, nullptr); + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, queueFamilies.data()); + + // find a graphics/present family + uint32_t graphicsFamily = UINT32_MAX; + for (size_t i = 0; i < queueFamilies.size(); i++) { + VkQueueFamilyProperties p = queueFamilies[i]; + + if (p.queueCount < 2) continue; // ideally have one queue for presenting and at least one other for rendering + + if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { + graphicsFamily = static_cast(i); + break; + } + } + } + if (graphicsFamily == UINT32_MAX) { + for (size_t i = 0; i < queueFamilies.size(); i++) { + VkQueueFamilyProperties p = queueFamilies[i]; + if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { + graphicsFamily = static_cast(i); + } + } + } + if (graphicsFamily == UINT32_MAX) { + throw std::runtime_error("Failed to find a graphics/present family!"); + } + LOG_WARN("Failed to find ideal graphics/present queue family! Falling back to family #{}.", graphicsFamily); + } + + // find a transfer queue family (image layout transitions, buffer upload) + uint32_t transferFamily = UINT32_MAX; + // prefer a dedicated transfer queue family + for (size_t i = 0; i < queueFamilies.size(); i++) { + VkQueueFamilyProperties p = queueFamilies[i]; + if (((p.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) && ((p.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) && ((p.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) { + transferFamily = static_cast(i); + break; + } + } + if (transferFamily == UINT32_MAX) { + transferFamily = graphicsFamily; + LOG_WARN("Failed to find a dedicated transfer queue family! Falling back to graphics family."); + } + + // queue priorities + std::vector graphicsQueuePriorities(queueFamilies[graphicsFamily].queueCount); + std::fill(graphicsQueuePriorities.begin(), graphicsQueuePriorities.end(), 1.0f); + std::vector transferQueuePriorities(queueFamilies[transferFamily].queueCount); + std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f); + + std::vector queueCreateInfos{}; + queueCreateInfos.push_back(VkDeviceQueueCreateInfo{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = graphicsFamily, + .queueCount = queueFamilies[graphicsFamily].queueCount, + .pQueuePriorities = graphicsQueuePriorities.data()}); + + if (transferFamily != graphicsFamily) { + queueCreateInfos.push_back(VkDeviceQueueCreateInfo{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = transferFamily, + .queueCount = queueFamilies[transferFamily].queueCount, + .pQueuePriorities = transferQueuePriorities.data()}); + } + + /* set enabled features */ + VkPhysicalDeviceMemoryPriorityFeaturesEXT memoryPriorityFeatures{}; + memoryPriorityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT; + memoryPriorityFeatures.memoryPriority = d.memoryPriorityFeature ? VK_TRUE : VK_FALSE; + VkPhysicalDeviceSynchronization2Features synchronization2Features{}; + synchronization2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; + synchronization2Features.pNext = &memoryPriorityFeatures; + synchronization2Features.synchronization2 = VK_TRUE; + VkPhysicalDeviceFeatures2 featuresToEnable{}; + featuresToEnable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featuresToEnable.pNext = &synchronization2Features; + featuresToEnable.features = requirements.requiredFeatures; + + /* get list of extensions to enable */ + std::vector extensionsToEnable{}; + for (const VkExtensionProperties& availableExt : availableExtensions) { + if (std::find(requirements.optionalExtensions.begin(), requirements.optionalExtensions.end(), std::string(availableExt.extensionName)) != + requirements.optionalExtensions.end()) { + extensionsToEnable.push_back(availableExt.extensionName); + } + } + extensionsToEnable.insert(extensionsToEnable.end(), requirements.requiredExtensions.begin(), requirements.requiredExtensions.end()); + + /* create device now */ + VkDeviceCreateInfo deviceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &featuresToEnable, + .flags = 0, + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledLayerCount = 0, // deprecated and ignored + .ppEnabledLayerNames = nullptr, // deprecated and ignored + .enabledExtensionCount = static_cast(extensionsToEnable.size()), + .ppEnabledExtensionNames = extensionsToEnable.data(), + .pEnabledFeatures = nullptr, + }; + + res = vkCreateDevice(d.physicalDevice, &deviceCreateInfo, nullptr, &d.device); + if (res != VK_SUCCESS) { + throw std::runtime_error("Unable to create Vulkan logical device, error code: " + std::to_string(res)); + } + + volkLoadDevice(d.device); + + /* get list of extensions enabled */ + d.enabledExtensions.clear(); + for (const char* ext : extensionsToEnable) { + // must be copied into std::strings + d.enabledExtensions.emplace_back(ext); + } + + if (transferFamily != graphicsFamily) { + vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); + if (queueFamilies[graphicsFamily].queueCount >= 2) { + d.queues.drawQueues.resize((size_t)queueFamilies[graphicsFamily].queueCount - 1); + for (uint32_t i = 0; i < d.queues.drawQueues.size(); i++) { + vkGetDeviceQueue(d.device, graphicsFamily, i + 1, &d.queues.drawQueues[i]); + } + } + else { + d.queues.drawQueues.resize(1); + d.queues.drawQueues[0] = d.queues.presentQueue; + } + d.queues.transferQueues.resize(queueFamilies[transferFamily].queueCount); + for (uint32_t i = 0; i < d.queues.transferQueues.size(); i++) { + vkGetDeviceQueue(d.device, transferFamily, i, &d.queues.transferQueues[i]); + } + } + else { + // same graphics family for graphics/present and transfer + uint32_t queueCount = queueFamilies[graphicsFamily].queueCount; + vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); + if (queueCount >= 2) { + d.queues.transferQueues.resize(1); + vkGetDeviceQueue(d.device, graphicsFamily, 1, &d.queues.transferQueues[0]); + // use the remaining queues for drawing + if (queueCount >= 3) { + d.queues.drawQueues.resize((size_t)queueCount - 2); + for (uint32_t i = 0; i < queueCount - 2; i++) { + vkGetDeviceQueue(d.device, graphicsFamily, i + 2, &d.queues.drawQueues[i]); + } + } + else { + // 2 queues available + // present and drawing share a queue + // transfer gets its own queue + d.queues.drawQueues.resize(1); + d.queues.drawQueues[0] = d.queues.presentQueue; + } + } + else { + // only 1 queue available :( + d.queues.transferQueues.resize(1); + d.queues.transferQueues[0] = d.queues.presentQueue; + d.queues.drawQueues.resize(1); + d.queues.drawQueues[0] = d.queues.presentQueue; + } + } + + d.queues.presentAndDrawQueueFamily = graphicsFamily; + d.queues.transferQueueFamily = transferFamily; + + return d; +} + +void destroyDevice(Device device) { vkDestroyDevice(device.device, nullptr); } + +} // namespace engine diff --git a/test/res/models/DamagedHelmet.glb b/test/res/models/DamagedHelmet.glb index 2cee76d..34b3330 100644 Binary files a/test/res/models/DamagedHelmet.glb and b/test/res/models/DamagedHelmet.glb differ diff --git a/test/res/models/ToyCar.glb b/test/res/models/ToyCar.glb index 42d648b..e8f5878 100644 Binary files a/test/res/models/ToyCar.glb and b/test/res/models/ToyCar.glb differ diff --git a/test/res/models/cube.glb b/test/res/models/cube.glb new file mode 100644 index 0000000..e7cc7b6 Binary files /dev/null and b/test/res/models/cube.glb differ diff --git a/test/res/models/floor.glb b/test/res/models/floor.glb new file mode 100644 index 0000000..8629572 Binary files /dev/null and b/test/res/models/floor.glb differ diff --git a/test/src/camera_controller.cpp b/test/src/camera_controller.cpp index 7931a3f..0692d0e 100644 --- a/test/src/camera_controller.cpp +++ b/test/src/camera_controller.cpp @@ -37,6 +37,10 @@ void CameraControllerSystem::OnUpdate(float ts) float dx = scene_->app()->input_manager()->GetAxis("movex") * CameraControllerComponent::kSpeedStrafe; float dy = scene_->app()->input_manager()->GetAxis("movey") * CameraControllerComponent::kSpeedForwardBack; + if (scene_->app()->input_manager()->GetButton("sprint")) { + dy *= CameraControllerComponent::kSprintMultiplier; + } + // calculate new pitch and yaw float d_pitch = scene_->app()->input_manager()->GetAxis("looky") * -1.0f * CameraControllerComponent::kCameraSensitivity; @@ -66,25 +70,41 @@ void CameraControllerSystem::OnUpdate(float ts) // check horizontal collisions first as otherwise the player may be teleported above a wall instead of colliding against it if (c->vel.x != 0.0f || c->vel.y != 0.0f) { // just in case, to avoid a ray with direction = (0,0,0) - engine::Ray horiz_ray{}; - horiz_ray.origin = t->position; // set origin to 'MaxStairHeight' units above player's feet - horiz_ray.origin.z += CameraControllerComponent::kMaxStairHeight - CameraControllerComponent::kPlayerHeight; - horiz_ray.direction.x = c->vel.x; - horiz_ray.direction.y = c->vel.y; // this is normalized by GetRayCast() - horiz_ray.direction.z = 0.0f; - const engine::Raycast horiz_raycast = scene_->GetSystem()->GetRaycast(horiz_ray); - if (horiz_raycast.hit) { - const glm::vec2 norm_xy = glm::normalize(glm::vec2{horiz_raycast.normal.x, horiz_raycast.normal.y}) * -1.0f; // make it point towards object + + std::array raycasts; + engine::Raycast* chosen_cast = nullptr; // nullptr means no hit at all + + float smallest_distance = std::numeric_limits::infinity(); + for (size_t i = 0; i < raycasts.size(); ++i) { + + const float lerp_value = static_cast(i) / (static_cast(CameraControllerComponent::kNumHorizontalRays) - 1); + + engine::Ray ray{}; + ray.origin = t->position; + ray.origin.z -= (CameraControllerComponent::kPlayerHeight - CameraControllerComponent::kMaxStairHeight) * lerp_value; + ray.direction.x = c->vel.x; + ray.direction.y = c->vel.y; // this is normalized by GetRayCast() + ray.direction.z = 0.0f; + raycasts[i] = scene_->GetSystem()->GetRaycast(ray); + + if (raycasts[i].hit && raycasts[i].distance < smallest_distance) { + smallest_distance = raycasts[i].distance; + chosen_cast = &raycasts[i]; + } + } + + if (chosen_cast != nullptr) { + const glm::vec2 norm_xy = glm::normalize(glm::vec2{chosen_cast->normal.x, chosen_cast->normal.y}) * -1.0f; // make it point towards object const glm::vec2 vel_xy = glm::vec2{ c->vel.x, c->vel.y }; // find the extent of the player's velocity in the direction of the wall's normal vector const glm::vec2 partial_vel = norm_xy * glm::dot(norm_xy, vel_xy); const glm::vec2 partial_dX = partial_vel * dt; - if (glm::length(partial_dX) > horiz_raycast.distance - CameraControllerComponent::kPlayerCollisionRadius) { + if (glm::length(partial_dX) > chosen_cast->distance - CameraControllerComponent::kPlayerCollisionRadius) { // player will collide with wall // push player out of collision zone - const glm::vec2 push_vector = glm::normalize(vel_xy) * fmaxf(CameraControllerComponent::kPlayerCollisionRadius, horiz_raycast.distance); - t->position.x = horiz_raycast.location.x - push_vector.x; - t->position.y = horiz_raycast.location.y - push_vector.y; + const glm::vec2 push_vector = glm::normalize(vel_xy) * fmaxf(CameraControllerComponent::kPlayerCollisionRadius, chosen_cast->distance); + t->position.x = chosen_cast->location.x - push_vector.x; + t->position.y = chosen_cast->location.y - push_vector.y; c->vel.x -= partial_vel.x; c->vel.y -= partial_vel.y; } @@ -103,7 +123,6 @@ void CameraControllerSystem::OnUpdate(float ts) const float mag_dz = fabsf(c->vel.z * dt); // check if the player will be less than 'height' units above the collided ground if (mag_dz > fall_raycast.distance - CameraControllerComponent::kMaxStairHeight) { - LOG_INFO("HIT"); // push player up to ground level and set as grounded t->position.z = fall_raycast.location.z + CameraControllerComponent::kPlayerHeight; c->vel.z = 0.0f; @@ -119,19 +138,25 @@ void CameraControllerSystem::OnUpdate(float ts) } else if (c->vel.z > 0.0f) { c->grounded = false; // they are jumping - } - - if (c->was_grounded != c->grounded) { - LOG_INFO("GROUNDED? {}", c->grounded); - c->was_grounded = c->grounded; + // check if intersection with ceiling + engine::Ray jump_ray{}; + jump_ray.origin = t->position; + jump_ray.direction = { 0.0f, 0.0f, 1.0f }; + const engine::Raycast jump_raycast = scene_->GetSystem()->GetRaycast(jump_ray); + if (jump_raycast.hit) { + // find how far the player will move (upwards) if velocity is applied without collision + const float mag_dz = fabsf(c->vel.z * dt); + // check if the player will be higher than the collided ground + if (mag_dz > jump_raycast.distance - CameraControllerComponent::kPlayerCollisionRadius) { + // push player below ceiling + t->position.z = jump_raycast.location.z - CameraControllerComponent::kPlayerCollisionRadius; + c->vel.z = 0.0f; + } + } } t->position += c->vel * dt; - if (glm::length(t->position) > CameraControllerComponent::kMaxDistanceFromOrigin) { - t->position = {0.0f, 0.0f, 100.0f}; - } - /* ROTATION STUFF */ // pitch quaternion @@ -155,15 +180,8 @@ void CameraControllerSystem::OnUpdate(float ts) /* user interface inputs */ - if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_P)) { - std::string pos_string{"x: " + std::to_string(t->world_matrix[3][0]) + " y: " + std::to_string(t->world_matrix[3][1]) + - " z: " + std::to_string(t->world_matrix[3][2])}; - LOG_INFO("position {}", pos_string); - LOG_INFO("rotation w: {} x: {} y: {} z: {}", t->rotation.w, t->rotation.x, t->rotation.y, t->rotation.z); - } - - if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_R)) { - t->position = {0.0f, 0.0f, 10.0f}; + if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_R) || glm::length(t->position) > CameraControllerComponent::kMaxDistanceFromOrigin) { + t->position = {0.0f, 0.0f, 100.0f}; c->vel = {0.0f, 0.0f, 0.0f}; c->pitch = glm::half_pi(); c->yaw = 0.0f; @@ -181,9 +199,7 @@ void CameraControllerSystem::OnUpdate(float ts) scene_->app()->scene_manager()->SetActiveScene(next_scene_); } - static std::vector perm_lines{}; - - if (scene_->app()->window()->GetButton(engine::inputs::MouseButton::M_LEFT)) { + if (scene_->app()->window()->GetButtonPress(engine::inputs::MouseButton::M_LEFT)) { engine::Ray ray{}; ray.origin = t->position; ray.direction = glm::vec3(glm::mat4_cast(t->rotation) * glm::vec4{0.0f, 0.0f, -1.0f, 1.0f}); @@ -195,24 +211,9 @@ void CameraControllerSystem::OnUpdate(float ts) LOG_INFO("Normal: {} {} {}", cast.normal.x, cast.normal.y, cast.normal.z); LOG_INFO("Ray direction: {} {} {}", ray.direction.x, ray.direction.y, ray.direction.z); LOG_INFO("Hit Entity: {}", scene_->GetComponent(cast.hit_entity)->tag); - perm_lines.emplace_back(ray.origin, cast.location, glm::vec3{0.0f, 0.0f, 1.0f}); - } - } - if (scene_->app()->window()->GetButtonPress(engine::inputs::MouseButton::M_RIGHT)) { - engine::Ray horiz_ray{}; - horiz_ray.origin = t->position; // set origin to 'MaxStairHeight' units above player's feet - horiz_ray.origin.z += CameraControllerComponent::kMaxStairHeight - CameraControllerComponent::kPlayerHeight; - horiz_ray.direction.x = c->vel.x; - horiz_ray.direction.y = c->vel.y; // this is normalized by GetRayCast() - horiz_ray.direction.z = 0.0f; - const engine::Raycast cast = scene_->GetSystem()->GetRaycast(horiz_ray); - if (cast.hit) { - LOG_INFO("Distance: {} m", cast.distance); - LOG_INFO("Location: {} {} {}", cast.location.x, cast.location.y, cast.location.z); - LOG_INFO("Normal: {} {} {}", cast.normal.x, cast.normal.y, cast.normal.z); - perm_lines.emplace_back(horiz_ray.origin, cast.location, glm::vec3{ 0.0f, 0.0f, 1.0f }); + c->perm_lines.emplace_back(ray.origin, cast.location, glm::vec3{0.0f, 0.0f, 1.0f}); } } - scene_->app()->debug_lines.insert(scene_->app()->debug_lines.end(), perm_lines.begin(), perm_lines.end()); + scene_->app()->debug_lines.insert(scene_->app()->debug_lines.end(), c->perm_lines.begin(), c->perm_lines.end()); } diff --git a/test/src/camera_controller.hpp b/test/src/camera_controller.hpp index 80eefb3..bae5614 100644 --- a/test/src/camera_controller.hpp +++ b/test/src/camera_controller.hpp @@ -4,38 +4,48 @@ #include #include "components/transform.h" +#include "application.h" #include "ecs.h" struct CameraControllerComponent { - static constexpr float kSpeedForwardBack = 4.0f; - static constexpr float kSpeedStrafe = 4.0f; - static constexpr float kCameraSensitivity = 0.007f; - static constexpr float kMaxDistanceFromOrigin = 10000.0f; - static constexpr float kGravAccel = -9.81f; - static constexpr float kPlayerHeight = 71.0f * 25.4f / 1000.0f; - static constexpr float kMaxStairHeight = 0.2f; - static constexpr float kPlayerCollisionRadius = 0.2f; - static constexpr float kMaxPitch = glm::pi(); - static constexpr float kMinPitch = 0.0f; - float yaw = 0.0f; - float pitch = glm::half_pi(); - glm::vec3 vel{ 0.0f, 0.0f, 0.0f }; - bool grounded = false; - bool was_grounded = false; + // looking + static constexpr float kCameraSensitivity = 0.007f; + static constexpr float kMaxPitch = glm::pi(); + static constexpr float kMinPitch = 0.0f; + + // moving + static constexpr float kSpeedForwardBack = 4.0f; + static constexpr float kSpeedStrafe = 4.0f; + static constexpr float kSprintMultiplier = 2.0f; + + // collision + static constexpr float kPlayerHeight = 2.0f; // 71.0f * 25.4f / 1000.0f; + static constexpr float kPlayerCollisionRadius = 0.2f; // this should be greater than z_near + static constexpr float kMaxStairHeight = 0.2f; + static constexpr size_t kNumHorizontalRays = 20; + + static constexpr float kGravAccel = -9.81f; + static constexpr float kMaxDistanceFromOrigin = 1000.0f; + + float yaw = 0.0f; + float pitch = glm::half_pi(); + glm::vec3 vel{0.0f, 0.0f, 0.0f}; + bool grounded = false; + + std::vector perm_lines{}; // raycasting lines }; -class CameraControllerSystem - : public engine::System { - public: - CameraControllerSystem(engine::Scene* scene); +class CameraControllerSystem : public engine::System { + public: + CameraControllerSystem(engine::Scene* scene); - // engine::System overrides - void OnUpdate(float ts) override; + // engine::System overrides + void OnUpdate(float ts) override; - engine::TransformComponent* t = nullptr; - CameraControllerComponent* c = nullptr; + engine::TransformComponent* t = nullptr; + CameraControllerComponent* c = nullptr; - engine::Scene* next_scene_ = nullptr; + engine::Scene* next_scene_ = nullptr; }; #endif \ No newline at end of file diff --git a/test/src/game.cpp b/test/src/game.cpp index a820224..1f72801 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -81,32 +81,15 @@ void PlayGame(GameSettings settings) * matrix */ auto camera_transform = main_scene->GetComponent(camera); - camera_transform->position = {-5.0f, -10.0f, 4.0f}; + camera_transform->position = {0.0f, 0.0f, 100.0f}; main_scene->RegisterComponent(); main_scene->RegisterSystem(); main_scene->AddComponent(camera); /* floor */ - engine::Entity floor = main_scene->CreateEntity("floor", 0, glm::vec3{-50.0f, -50.0f, 0.0f}); - auto floor_renderable = main_scene->AddComponent(floor); - floor_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 100.0f, 100.0f, 0.1f, 50.0f); - floor_renderable->material = std::make_unique(app.renderer(), app.GetResource("builtin.fancy")); - std::shared_ptr floor_albedo = - engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-albedo.png"), engine::gfx::SamplerInfo{}, app.renderer()); - std::shared_ptr floor_normal = - engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-normal.png"), engine::gfx::SamplerInfo{}, app.renderer(), false); - std::shared_ptr floor_mr = - engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-roughness.png"), engine::gfx::SamplerInfo{}, app.renderer(), false); - floor_renderable->material->SetAlbedoTexture(floor_albedo); - floor_renderable->material->SetNormalTexture(floor_normal); - floor_renderable->material->SetMetallicRoughnessTexture(floor_mr); - floor_renderable->material->SetOcclusionTexture(app.GetResource("builtin.white")); - floor_renderable->visible = true ; - auto floor_col = main_scene->AddComponent(floor); - floor_col->aabb.min = glm::vec3{0.0f, 0.0f, 0.0f}; - floor_col->aabb.max = glm::vec3{100.0f, 100.0f, 0.1f }; - + engine::Entity floor = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/floor.glb")); + main_scene->GetComponent(main_scene->GetEntity("Cube", floor))->visible = false; engine::Entity monke = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/monke.glb")); main_scene->GetComponent(monke)->position.y += 10.0f; @@ -116,23 +99,13 @@ void PlayGame(GameSettings settings) //main_scene->GetComponent(bottle)->position.x += 25.0f; //main_scene->GetComponent(bottle)->position.z += 5.0f; - /* skybox */ - engine::Entity skybox = main_scene->CreateEntity("skybox"); - auto skybox_transform = main_scene->GetComponent(skybox); - skybox_transform->is_static = true; - skybox_transform->position = { -5.0f, -5.0f, -5.0f }; - auto skybox_renderable = main_scene->AddComponent(skybox); - skybox_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 10.0f, 10.0f, 10.0f, 1.0f, true); - skybox_renderable->material = std::make_unique(app.renderer(), app.GetResource("builtin.skybox")); - skybox_renderable->material->SetAlbedoTexture(app.GetResource("builtin.black")); - engine::Entity helmet = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/DamagedHelmet.glb")); main_scene->GetPosition(helmet) += glm::vec3{5.0f, 5.0f, 1.0f}; main_scene->GetScale(helmet) *= 3.0f; engine::Entity toycar = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/ToyCar.glb")); - main_scene->GetScale(toycar) *= 100.0f; - main_scene->GetPosition(toycar).z += 1.8f; + main_scene->GetScale(toycar) *= 150.0f; + main_scene->GetPosition(toycar).z -= 0.07f; engine::Entity stairs = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/stairs.glb")); main_scene->GetPosition(stairs) += glm::vec3{-8.0f, -5.0f, 0.1f};