Add skybox and crude IBL approximation

This commit is contained in:
bailehuni 2024-03-19 11:32:51 +00:00
parent 1b0af6cd8b
commit 11641c6179
30 changed files with 1191 additions and 724 deletions

View File

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

View File

@ -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<Bits>::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<VertexAttribDescription> attribute_descriptions;
uint32_t stride;
std::vector<VertexAttribDescription> 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<const DescriptorSetLayout*> 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<const DescriptorSetLayout*> 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<engine::gfx::SamplerInfo> {
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<int>()(static_cast<int>(k.minify));
size_t h2 = hash<int>()(static_cast<int>(k.magnify));
size_t h3 = hash<int>()(static_cast<int>(k.mipmap));
size_t h4 = hash<bool>()(k.anisotropic_filtering);
size_t h1 = hash<int>()(static_cast<int>(k.minify));
size_t h2 = hash<int>()(static_cast<int>(k.magnify));
size_t h3 = hash<int>()(static_cast<int>(k.mipmap));
size_t h4 = hash<int>()(static_cast<int>(k.wrap_u));
size_t h5 = hash<int>()(static_cast<int>(k.wrap_v));
size_t h6 = hash<int>()(static_cast<int>(k.wrap_w));
size_t h7 = hash<bool>()(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

View File

@ -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<const void*, 6>& image_data);
void DestroyImage(const gfx::Image* image);
const gfx::Sampler* CreateSampler(const gfx::SamplerInfo& info);

View File

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

View File

@ -19,8 +19,7 @@ class Material {
void SetAlbedoTexture(std::shared_ptr<Texture> texture);
void SetNormalTexture(std::shared_ptr<Texture> texture);
void SetOcclusionTexture(std::shared_ptr<Texture> texture);
void SetMetallicRoughnessTexture(std::shared_ptr<Texture> texture);
void SetOcclusionRoughnessMetallicTexture(std::shared_ptr<Texture> texture);
const gfx::DescriptorSet* GetDescriptorSet() { return material_set_; }
Shader* GetShader() { return shader_.get(); }
@ -29,8 +28,7 @@ class Material {
const std::shared_ptr<Shader> shader_;
std::shared_ptr<Texture> texture_albedo_;
std::shared_ptr<Texture> texture_normal_;
std::shared_ptr<Texture> texture_occlusion_;
std::shared_ptr<Texture> texture_metallic_roughness_;
std::shared_ptr<Texture> texture_occlusion_roughness_metallic_;
const gfx::DescriptorSet* material_set_ = nullptr;

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

View File

@ -120,21 +120,6 @@ Application::Application(const char* appName, const char* appVersion, gfx::Graph
std::make_unique<Shader>(renderer(), GetResourcePath("engine/shaders/fancy.vert"), GetResourcePath("engine/shaders/fancy.frag"), shaderSettings);
GetResourceManager<Shader>()->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<Shader>(renderer(), GetResourcePath("engine/shaders/skybox.vert"), GetResourcePath("engine/shaders/skybox.frag"), shaderSettings);
GetResourceManager<Shader>()->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<CollisionSystem>()) {
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<CollisionSystem>()) {
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<TransformComponent>(scene->GetEntity("camera"))->world_matrix;
auto mesh_render_system = scene->GetSystem<MeshRenderSystem>();
static_list = mesh_render_system->GetStaticRenderList();

View File

@ -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<const void*, 6>& 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<uint32_t>(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<std::byte*>(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, &region);
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;

View File

@ -1,6 +1,9 @@
#include "renderer.h"
#include <array>
#include "application_component.h"
#include "util/files.h"
#include <glm/mat4x4.hpp>
#include <glm/trigonometric.hpp>
@ -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<gfx::DescriptorSetLayoutBinding> 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<std::unique_ptr<std::vector<uint8_t>>, 6> face_unq_ptrs{};
std::array<const void*, 6> 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<glm::vec3> 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);

View File

@ -23,16 +23,10 @@ void Material::SetNormalTexture(std::shared_ptr<Texture> texture)
texture_normal_ = texture;
}
void Material::SetOcclusionTexture(std::shared_ptr<Texture> texture)
void Material::SetOcclusionRoughnessMetallicTexture(std::shared_ptr<Texture> texture)
{
renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 2, texture->GetImage(), texture->GetSampler());
texture_occlusion_ = texture;
}
void Material::SetMetallicRoughnessTexture(std::shared_ptr<Texture> texture)
{
renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 3, texture->GetImage(), texture->GetSampler());
texture_metallic_roughness_ = texture;
texture_occlusion_roughness_metallic_ = texture;
}
} // namespace engine

View File

@ -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<Texture>("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<Texture>(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<Texture>("builtin.white")); // default metal = 1.0, rough = 1.0
// occlusion roughness metallic
materials.back()->SetOcclusionRoughnessMetallicTexture(scene.app()->GetResource<Texture>("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<double> mr_values{1.0f, material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.metallicFactor, 1.0f};
LOG_INFO("Creating occlusion roughness metallic texture...");
const std::vector<double> 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<Texture>(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<Texture>("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);

View File

@ -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<uint32_t>(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<VkPhysicalDevice> physicalDevices(physDeviceCount);
res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, physicalDevices.data());
assert(res == VK_SUCCESS);
std::vector<VkExtensionProperties> 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<VkQueueFamilyProperties> 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<uint32_t>(i))) {
graphicsFamily = static_cast<uint32_t>(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<uint32_t>(i))) {
graphicsFamily = static_cast<uint32_t>(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<uint32_t>(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<float> graphicsQueuePriorities(queueFamilies[graphicsFamily].queueCount);
std::fill(graphicsQueuePriorities.begin(), graphicsQueuePriorities.end(), 1.0f);
std::vector<float> transferQueuePriorities(queueFamilies[transferFamily].queueCount);
std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f);
std::vector<VkDeviceQueueCreateInfo> 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<const char*> 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<uint32_t>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledLayerCount = 0, // deprecated and ignored
.ppEnabledLayerNames = nullptr, // deprecated and ignored
.enabledExtensionCount = static_cast<uint32_t>(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<uint32_t>(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<VkPhysicalDevice> physicalDevices(physDeviceCount);
res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, physicalDevices.data());
assert(res == VK_SUCCESS);
std::vector<VkExtensionProperties> 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<VkQueueFamilyProperties> 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<uint32_t>(i))) {
graphicsFamily = static_cast<uint32_t>(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<uint32_t>(i))) {
graphicsFamily = static_cast<uint32_t>(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<uint32_t>(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<float> graphicsQueuePriorities(queueFamilies[graphicsFamily].queueCount);
std::fill(graphicsQueuePriorities.begin(), graphicsQueuePriorities.end(), 1.0f);
std::vector<float> transferQueuePriorities(queueFamilies[transferFamily].queueCount);
std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f);
std::vector<VkDeviceQueueCreateInfo> 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<const char*> 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<uint32_t>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledLayerCount = 0, // deprecated and ignored
.ppEnabledLayerNames = nullptr, // deprecated and ignored
.enabledExtensionCount = static_cast<uint32_t>(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

Binary file not shown.

Binary file not shown.

BIN
test/res/models/cube.glb Normal file

Binary file not shown.

BIN
test/res/models/floor.glb Normal file

Binary file not shown.

View File

@ -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<engine::CollisionSystem>()->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<engine::Raycast, CameraControllerComponent::kNumHorizontalRays> raycasts;
engine::Raycast* chosen_cast = nullptr; // nullptr means no hit at all
float smallest_distance = std::numeric_limits<float>::infinity();
for (size_t i = 0; i < raycasts.size(); ++i) {
const float lerp_value = static_cast<float>(i) / (static_cast<float>(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<engine::CollisionSystem>()->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<engine::CollisionSystem>()->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<float>();
c->yaw = 0.0f;
@ -181,9 +199,7 @@ void CameraControllerSystem::OnUpdate(float ts)
scene_->app()->scene_manager()->SetActiveScene(next_scene_);
}
static std::vector<engine::Line> 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<engine::TransformComponent>(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<engine::CollisionSystem>()->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());
}

View File

@ -4,38 +4,48 @@
#include <glm/vec3.hpp>
#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<float>();
static constexpr float kMinPitch = 0.0f;
float yaw = 0.0f;
float pitch = glm::half_pi<float>();
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<float>();
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<float>();
glm::vec3 vel{0.0f, 0.0f, 0.0f};
bool grounded = false;
std::vector<engine::Line> 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

View File

@ -81,32 +81,15 @@ void PlayGame(GameSettings settings)
* matrix */
auto camera_transform = main_scene->GetComponent<engine::TransformComponent>(camera);
camera_transform->position = {-5.0f, -10.0f, 4.0f};
camera_transform->position = {0.0f, 0.0f, 100.0f};
main_scene->RegisterComponent<CameraControllerComponent>();
main_scene->RegisterSystem<CameraControllerSystem>();
main_scene->AddComponent<CameraControllerComponent>(camera);
/* floor */
engine::Entity floor = main_scene->CreateEntity("floor", 0, glm::vec3{-50.0f, -50.0f, 0.0f});
auto floor_renderable = main_scene->AddComponent<engine::MeshRenderableComponent>(floor);
floor_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 100.0f, 100.0f, 0.1f, 50.0f);
floor_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.fancy"));
std::shared_ptr<engine::Texture> floor_albedo =
engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-albedo.png"), engine::gfx::SamplerInfo{}, app.renderer());
std::shared_ptr<engine::Texture> floor_normal =
engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-normal.png"), engine::gfx::SamplerInfo{}, app.renderer(), false);
std::shared_ptr<engine::Texture> 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<engine::Texture>("builtin.white"));
floor_renderable->visible = true ;
auto floor_col = main_scene->AddComponent<engine::ColliderComponent>(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<engine::MeshRenderableComponent>(main_scene->GetEntity("Cube", floor))->visible = false;
engine::Entity monke = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/monke.glb"));
main_scene->GetComponent<engine::TransformComponent>(monke)->position.y += 10.0f;
@ -116,23 +99,13 @@ void PlayGame(GameSettings settings)
//main_scene->GetComponent<engine::TransformComponent>(bottle)->position.x += 25.0f;
//main_scene->GetComponent<engine::TransformComponent>(bottle)->position.z += 5.0f;
/* skybox */
engine::Entity skybox = main_scene->CreateEntity("skybox");
auto skybox_transform = main_scene->GetComponent<engine::TransformComponent>(skybox);
skybox_transform->is_static = true;
skybox_transform->position = { -5.0f, -5.0f, -5.0f };
auto skybox_renderable = main_scene->AddComponent<engine::MeshRenderableComponent>(skybox);
skybox_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 10.0f, 10.0f, 10.0f, 1.0f, true);
skybox_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.skybox"));
skybox_renderable->material->SetAlbedoTexture(app.GetResource<engine::Texture>("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};