From 41eb6368265d5cf11acc4d2caa5e9523480b210d Mon Sep 17 00:00:00 2001 From: Shu Date: Sun, 27 Sep 2020 00:05:43 +0200 Subject: [PATCH] Just a Vulkan triangle --- .gitattributes | 2 + CMakeLists.txt | 4 +- README.md | 1 + TODO.md | 3 +- resource/content/shaders/Tris.fs.spv | 3 + resource/content/shaders/Tris.vs.spv | 3 + resource/shaders-src/Tris.fs | 10 + resource/shaders-src/Tris.vs | 21 + resource/shaders-src/Voxel.fs | 227 ++++++++++ resource/shaders-src/Voxel.gs | 71 +++ resource/shaders-src/Voxel.vs | 76 ++++ resource/shaders-src/compile.sh | 29 ++ src/client/Client.cpp | 21 +- src/client/Window.cpp | 6 + src/client/Window.hpp | 1 + src/client/config.hpp | 10 +- src/client/render/Pipeline.hpp | 45 -- src/client/render/Renderer.hpp | 74 ++- src/client/render/buffer/Abstract.hpp | 14 + src/client/render/gl/Pipeline.cpp | 128 ------ src/client/render/gl/Pipeline.hpp | 97 ---- src/client/render/gl/Renderer.cpp | 161 ++++++- src/client/render/gl/Renderer.hpp | 89 +++- src/client/render/gl/UI.cpp | 1 + src/client/render/gl/UI.hpp | 3 +- src/client/render/gl/buffer/Abstract.hpp | 11 +- src/client/render/gl/pass/ColorProgram.cpp | 4 +- src/client/render/gl/pass/ColorProgram.hpp | 2 +- src/client/render/gl/pass/EntityProgram.cpp | 4 +- src/client/render/gl/pass/EntityProgram.hpp | 2 +- src/client/render/gl/pass/Program.hpp | 5 +- src/client/render/gl/pass/SkyProgram.cpp | 6 +- src/client/render/gl/pass/SkyProgram.hpp | 4 +- src/client/render/gl/pass/VoxelProgram.cpp | 10 +- src/client/render/gl/pass/VoxelProgram.hpp | 24 +- src/client/render/gl/pass/WorldProgram.cpp | 4 +- src/client/render/gl/pass/WorldProgram.hpp | 2 +- src/client/render/index.cpp | 28 ++ src/client/render/index.hpp | 8 + src/client/render/vk/CommandPool.cpp | 129 ++++++ src/client/render/vk/CommandPool.hpp | 29 ++ src/client/render/vk/Pipeline.cpp | 238 ++++++++++ src/client/render/vk/Pipeline.hpp | 25 + src/client/render/vk/Renderer.cpp | 476 ++++++++++++++++++++ src/client/render/vk/Renderer.hpp | 64 +++ src/client/render/vk/SwapChain.cpp | 136 ++++++ src/client/render/vk/SwapChain.hpp | 28 ++ src/client/render/vk/UI.cpp | 30 ++ src/client/render/vk/UI.hpp | 24 + src/client/render/vk/forward.hpp | 10 + src/client/render/vk/shared.hpp | 31 ++ src/client/world/index.cpp | 1 + src/core/config.hpp | 5 +- src/core/net/Client.hpp | 2 +- 54 files changed, 2063 insertions(+), 379 deletions(-) create mode 100644 resource/content/shaders/Tris.fs.spv create mode 100644 resource/content/shaders/Tris.vs.spv create mode 100644 resource/shaders-src/Tris.fs create mode 100644 resource/shaders-src/Tris.vs create mode 100644 resource/shaders-src/Voxel.fs create mode 100644 resource/shaders-src/Voxel.gs create mode 100644 resource/shaders-src/Voxel.vs create mode 100755 resource/shaders-src/compile.sh delete mode 100644 src/client/render/Pipeline.hpp create mode 100644 src/client/render/buffer/Abstract.hpp delete mode 100644 src/client/render/gl/Pipeline.cpp delete mode 100644 src/client/render/gl/Pipeline.hpp create mode 100644 src/client/render/index.cpp create mode 100644 src/client/render/index.hpp create mode 100644 src/client/render/vk/CommandPool.cpp create mode 100644 src/client/render/vk/CommandPool.hpp create mode 100644 src/client/render/vk/Pipeline.cpp create mode 100644 src/client/render/vk/Pipeline.hpp create mode 100644 src/client/render/vk/Renderer.cpp create mode 100644 src/client/render/vk/Renderer.hpp create mode 100644 src/client/render/vk/SwapChain.cpp create mode 100644 src/client/render/vk/SwapChain.hpp create mode 100644 src/client/render/vk/UI.cpp create mode 100644 src/client/render/vk/UI.hpp create mode 100644 src/client/render/vk/forward.hpp create mode 100644 src/client/render/vk/shared.hpp diff --git a/.gitattributes b/.gitattributes index d979448..d81e1ad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,3 +9,5 @@ *.tga filter=lfs diff=lfs merge=lfs -text *.tiff filter=lfs diff=lfs merge=lfs -text *.BMP filter=lfs diff=lfs merge=lfs -text +# Shaders +*.spv filter=lfs diff=lfs merge=lfs -text diff --git a/CMakeLists.txt b/CMakeLists.txt index a94059e..a6047b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp" "include/tracy/TracyClient.cpp") set(CORE_HEADERS "include/toml++" "include/robin_hood" "include/libguarded" "include/tracy") set(CORE_LIBS pthread dl glm::glm_static enet::enet_static zstd::zstd_static) -file(GLOB_RECURSE CLIENT_SOURCES "src/client/*.cpp" "include/imgui/*.cpp" "include/meshoptimizer/*.cpp" "include/gl3w/gl3w.c") -set(CLIENT_HEADERS "include/imgui" "include/meshoptimizer" "include/gl3w") +file(GLOB_RECURSE CLIENT_SOURCES "src/client/*.cpp" "include/imgui/*.cpp" "include/meshoptimizer/*.cpp" "include/gl3w/gl3w.c" "include/volk/volk.c") +set(CLIENT_HEADERS "include/imgui" "include/meshoptimizer" "include/gl3w" "include/volk") set(CLIENT_LIBS glfw) file(GLOB_RECURSE SERVER_SOURCES "src/server/*.cpp" "include/FastNoiseSIMD/*.cpp") diff --git a/README.md b/README.md index 34608ed..e7d8720 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ To get a local copy up and running, follow these simple steps. * Python: utility scripts * Tracy v0.7: profiling +* glslc: compile vk shaders ### Installation diff --git a/TODO.md b/TODO.md index d3facec..0f4374f 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,8 @@ ## Hello screen again - [~] Extract OpenGL - - [ ] Minimal Vulkan + - Rename Passes/Programs as Pipelines + - [~] Minimal Vulkan - [ ] ImGui - [ ] Config (yaml) diff --git a/resource/content/shaders/Tris.fs.spv b/resource/content/shaders/Tris.fs.spv new file mode 100644 index 0000000..e223b25 --- /dev/null +++ b/resource/content/shaders/Tris.fs.spv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71d966265a11ee35ec6f5b58769526362c0369ab876154cbb7245a1e076252b1 +size 608 diff --git a/resource/content/shaders/Tris.vs.spv b/resource/content/shaders/Tris.vs.spv new file mode 100644 index 0000000..2e150a5 --- /dev/null +++ b/resource/content/shaders/Tris.vs.spv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2d81db6472dd4bb993a716c95c2e9714453ff67f2c3d2deab74f80b73285713 +size 1540 diff --git a/resource/shaders-src/Tris.fs b/resource/shaders-src/Tris.fs new file mode 100644 index 0000000..84daf5e --- /dev/null +++ b/resource/shaders-src/Tris.fs @@ -0,0 +1,10 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/resource/shaders-src/Tris.vs b/resource/shaders-src/Tris.vs new file mode 100644 index 0000000..0439d32 --- /dev/null +++ b/resource/shaders-src/Tris.vs @@ -0,0 +1,21 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file diff --git a/resource/shaders-src/Voxel.fs b/resource/shaders-src/Voxel.fs new file mode 100644 index 0000000..34a2581 --- /dev/null +++ b/resource/shaders-src/Voxel.fs @@ -0,0 +1,227 @@ +#version 450 core + +layout (constant_id = 0) const bool FOG = true; +layout (constant_id = 1) const bool PBR = true; +layout (constant_id = 2) const bool TRIPLANAR = false; +layout (constant_id = 3) const bool STOCHASTIC = false; +layout (constant_id = 4) const bool BLEND = true; + +layout (constant_id = 16) UNIT_SIZE = 8; + +// Ouput data +layout(location = 0) out vec3 color; + +uniform sampler2DArray TextureAtlas; +uniform sampler2DArray NormalAtlas; +uniform sampler2DArray HOSAtlas; + +uniform mat4 View; +uniform vec3 FogColor; + +#ifdef GEOMETRY +in GeometryData +#else +in VertexData +#endif +{ + vec3 Position_modelspace; +#ifdef GEOMETRY + flat uint Textures[3]; + vec3 TextureRatio; +#else + flat uint Texture; +#endif + + vec3 FaceNormal_modelspace; + vec3 FaceNormal_worldspace; + vec3 EyeDirection_cameraspace; + vec3 LightDirection_cameraspace; + + float Depth; +} vs; + +vec3 expand(vec3 v) { + return (v - 0.5) * 2; +} + +vec2 hash2D(vec2 s) { + // magic numbers + return fract(sin(mod(vec2(dot(s, vec2(127.1, 311.7)), dot(s, vec2(269.5, 183.3))), 3.14159)) * 43758.5453); +} + +vec4 textureStochastic(sampler2DArray sample, vec3 UV) { +if(STOCHASTIC) { + // triangular by approx 2*sqrt(3) + vec2 skewUV = mat2(1.0, 0.0, -0.57735027, 1.15470054) * (UV.xy * 3.46400); + + // vertex id and barrycentric coords + vec2 vxID = vec2(floor(skewUV)); + vec3 barry = vec3(fract(skewUV), 0); + barry.z = 1.0 - barry.x - barry.y; + + vec3 BW_vx0; + vec3 BW_vx1; + vec3 BW_vx2; + vec3 BW_vx3; + + if(barry.z > 0) { + BW_vx0 = vec3(vxID, 0); + BW_vx1 = vec3(vxID + vec2(0, 1), 0); + BW_vx2 = vec3(vxID + vec2(1, 0), 0); + BW_vx3 = barry.zyx; + } else { + BW_vx0 = vec3(vxID + vec2(1, 1), 0); + BW_vx1 = vec3(vxID + vec2(1, 0), 0); + BW_vx2 = vec3(vxID + vec2(0, 1), 0); + BW_vx3 = vec3(-barry.z, 1.0 - barry.y, 1.0 - barry.x); + } + + vec2 dx = dFdx(UV.xy); + vec2 dy = dFdy(UV.xy); + + return textureGrad(sample, vec3(UV.xy + hash2D(BW_vx0.xy), UV.z), dx, dy) * BW_vx3.x + + textureGrad(sample, vec3(UV.xy + hash2D(BW_vx1.xy), UV.z), dx, dy) * BW_vx3.y + + textureGrad(sample, vec3(UV.xy + hash2D(BW_vx2.xy), UV.z), dx, dy) * BW_vx3.z; +} else { + return texture(sample, UV); +} +} + +vec4 getTexture(sampler2DArray sample, vec2 UV) { +#ifdef GEOMETRY +if(BLEND) { + vec4 colx = textureStochastic(sample, vec3(UV, vs.Textures[0])); + if(vs.Textures[1] == vs.Textures[0]) { + return vs.Textures[2] == vs.Textures[0] ? colx : + mix(colx, textureStochastic(sample, vec3(UV, vs.Textures[2])), vs.TextureRatio.z); + } else { + vec4 coly = textureStochastic(sample, vec3(UV, vs.Textures[1])); + return vs.Textures[2] == vs.Textures[0] ? mix(colx, coly, vs.TextureRatio.y) : ( + vs.Textures[2] == vs.Textures[1] ? mix(coly, colx, vs.TextureRatio.x) : + colx * vs.TextureRatio.x + coly * vs.TextureRatio.y + textureStochastic(sample, vec3(UV, vs.Textures[2])) * vs.TextureRatio.z); + } +} else { + int mainTexture = vs.TextureRatio.x >= vs.TextureRatio.y ? + (vs.TextureRatio.x >= vs.TextureRatio.z ? 0 : 2) : + (vs.TextureRatio.y >= vs.TextureRatio.z ? 1 : 2); + return textureStochastic(sample, vec3(UV, vs.Textures[mainTexture])); +} +#else + return textureStochastic(sample, vec3(UV, vs.Texture)); +#endif +} + +vec3 getTriTexture(sampler2DArray sample, vec2 crdx, vec2 crdy, vec2 crdz, vec3 weights) { + return getTexture(sample, crdx).rgb * weights.x + + getTexture(sample, crdy).rgb * weights.y + + getTexture(sample, crdz).rgb * weights.z; +} + +void main() { + float texScale = 1. / UNIT_SIZE; +if(TRIPLANAR) { + // Triplanar + float plateauSize = 0.001; + float transitionSpeed = 2; + + vec3 blendWeights = abs(vs.FaceNormal_modelspace); + blendWeights = blendWeights - plateauSize; + blendWeights = pow(max(blendWeights, 0), vec3(transitionSpeed)); + vec2 UVx = vs.Position_modelspace.yz * texScale; + vec2 UVy = vs.Position_modelspace.zx * texScale; + vec2 UVz = vs.Position_modelspace.xy * texScale; + + vec3 tex = getTriTexture(TextureAtlas, UVx, UVy, UVz, blendWeights); + +if(PBR) { + // Whiteout normal blend + vec3 texNx = expand(getTexture(NormalAtlas, UVx).rgb); + vec3 texNy = expand(getTexture(NormalAtlas, UVy).rgb); + vec3 texNz = expand(getTexture(NormalAtlas, UVz).rgb); + // Swizzle world normals into tangent space and apply Whiteout blend + texNx = vec3(texNx.xy + vs.FaceNormal_worldspace.zy, abs(texNx.z) * vs.FaceNormal_worldspace.x); + texNy = vec3(texNy.xy + vs.FaceNormal_worldspace.xz, abs(texNy.z) * vs.FaceNormal_worldspace.y); + texNz = vec3(texNz.xy + vs.FaceNormal_worldspace.xy, abs(texNz.z) * vs.FaceNormal_worldspace.z); + // Swizzle tangent normals to match world orientation and triblend + vec3 worldNormal = normalize(texNx.zyx * blendWeights.x + texNy.xzy * blendWeights.y +texNz.xyz * blendWeights.z); + + vec3 texHOS = getTriTexture(HOSAtlas, UVx, UVy, UVz, blendWeights); +} +} else { + // Cheap planar + vec3 blendWeights = abs(vs.FaceNormal_modelspace); + vec3 nrm = normalize(pow(blendWeights, vec3(80 / sqrt(UNIT_SIZE)))); + vec2 UV = (vec2(vs.Position_modelspace.xy * nrm.z) + vec2(vs.Position_modelspace.yz * nrm.x) + vec2(vs.Position_modelspace.zx * nrm.y)) * texScale; + + vec3 tex = getTexture(TextureAtlas, UV).rgb; +if(PBR) { + vec3 texN = expand(getTexture(NormalAtlas, UV).rgb); + // Swizzle world normals into tangent space and apply Whiteout blend + // Swizzle tangent normals to match world orientation and triblend + vec3 worldNormal = normalize(vec3(texN.xy + vs.FaceNormal_worldspace.zy, abs(texN.z) * vs.FaceNormal_worldspace.x).zyx * blendWeights.x + + vec3(texN.xy + vs.FaceNormal_worldspace.xz, abs(texN.z) * vs.FaceNormal_worldspace.y).xzy * blendWeights.y + + vec3(texN.xy + vs.FaceNormal_worldspace.xy, abs(texN.z) * vs.FaceNormal_worldspace.z).xyz * blendWeights.z); + + vec3 texHOS = getTexture(HOSAtlas, UV).rgb; +} +} + +// Colors +if(PBR) { + // Texture properties + vec3 TextureDiffuseColor = tex; + vec3 TextureAmbientColor = vec3(.1) * TextureDiffuseColor * texHOS.y; + vec3 TextureSpecularColor = vec3(.8) * texHOS.z; + + vec3 Normal_cameraspace = normalize((View * vec4(worldNormal,0)).xyz); + + // Light emission properties + // You probably want to put them as uniforms + vec3 LightColor = vec3(1, 0.9, 0.9); + float LightPower = 1.2f; + + // Distance to the light + float distance = 1.0f;//length( LightPosition_worldspace - Position_worldspace ); + + // Direction of the light (from the fragment to the light) + vec3 l = normalize(vs.LightDirection_cameraspace); + // Cosine of the angle between the normal and the light direction, + // clamped above 0 + // - light is at the vertical of the triangle -> 1 + // - light is perpendiular to the triangle -> 0 + // - light is behind the triangle -> 0 + float cosTheta = clamp(dot(Normal_cameraspace,l), 0,1 ); + + // Eye vector (towards the camera) + vec3 E = normalize(vs.EyeDirection_cameraspace); + // Direction in which the triangle reflects the light + vec3 R = reflect(-l,Normal_cameraspace); + // Cosine of the angle between the Eye vector and the Reflect vector, + // clamped to 0 + // - Looking into the reflection -> 1 + // - Looking elsewhere -> < 1 + float cosAlpha = clamp( dot( E,R ), 0,1 ); + + float visibility=1.0; + + // MAYBE: shadow + + color = + // Ambient : simulates indirect lighting + TextureAmbientColor + + // Diffuse : "color" of the object + visibility * TextureDiffuseColor * LightColor * LightPower * cosTheta / (distance * distance) + + // Specular : reflective highlight, like a mirror + visibility * TextureSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance * distance); +} else { + color = tex; +} +if(FOG) { + float ratio = exp(vs.Depth * 0.69)-1; + color = mix(color, pow(FogColor, vec3(2.2)), clamp(ratio, 0, 1)); +} + color = pow(color, vec3(1.0 / 2.2)); + if(color.r > 1 || color.g > 1 || color.b > 1) { + color = vec3(1, 0, 0); //TODO: bloom + } +} \ No newline at end of file diff --git a/resource/shaders-src/Voxel.gs b/resource/shaders-src/Voxel.gs new file mode 100644 index 0000000..de33fc0 --- /dev/null +++ b/resource/shaders-src/Voxel.gs @@ -0,0 +1,71 @@ +#version 450 core + +layout (constant_id = 0) const bool FOG = true; +layout (constant_id = 1) const bool PBR = true; + +layout (triangles) in; +layout (triangle_strip, max_vertices = 3) out; + +in VertexData { + vec3 Position_modelspace; + flat uint Texture; + vec3 FaceNormal_modelspace; + + vec3 FaceNormal_worldspace; + vec3 EyeDirection_cameraspace; + vec3 LightDirection_cameraspace; + + float Depth; +} vs_in[]; + +out GeometryData { + vec3 Position_modelspace; + flat uint Textures[3]; + vec3 TextureRatio; + vec3 FaceNormal_modelspace; + + vec3 FaceNormal_worldspace; + vec3 EyeDirection_cameraspace; + vec3 LightDirection_cameraspace; + + float Depth; +} gs; + +void main() { + for(int i = 0; i < gl_in.length(); i++) { + + gl_Position = gl_in[i].gl_Position; + gs.Position_modelspace = vs_in[i].Position_modelspace; + gs.FaceNormal_modelspace = vs_in[i].FaceNormal_modelspace; + + gs.Textures[i] = vs_in[i].Texture; + switch(int(mod(i,3))) { + case 0: + gs.TextureRatio = vec3(1,0,0); + break; + case 1: + gs.TextureRatio = vec3(0,1,0); + break; + case 2: + gs.TextureRatio = vec3(0,0,1); + break; + default: + gs.TextureRatio = vec3(0); + break; + }; + +if(PBR) { + gs.FaceNormal_worldspace = vs_in[i].FaceNormal_worldspace; + gs.EyeDirection_cameraspace = vs_in[i].EyeDirection_cameraspace; + gs.LightDirection_cameraspace = vs_in[i].LightDirection_cameraspace; +} +#ifdef SHADOW + gs.ShadowCoord = vs_in[i].ShadowCoord; +#endif +if(FOG) { + gs.Depth = vs_in[i].Depth; +} + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/resource/shaders-src/Voxel.vs b/resource/shaders-src/Voxel.vs new file mode 100644 index 0000000..2d4294d --- /dev/null +++ b/resource/shaders-src/Voxel.vs @@ -0,0 +1,76 @@ +#version 450 core + +layout (constant_id = 0) const bool FOG = true; +layout (constant_id = 1) const bool PBR = true; +// FS spe +layout (constant_id = 5) const bool DO_CURVATURE = false; +layout (constant_id = 6) const bool CURV_DEPTH = true; + +layout(location = 0) in vec3 Position_modelspace; +layout(location = 1) in uint Texture_model; +layout(location = 2) in vec3 Normal_modelspace; + +out VertexData { + vec3 Position_modelspace; + flat uint Texture; + vec3 FaceNormal_modelspace; + + vec3 FaceNormal_worldspace; + vec3 EyeDirection_cameraspace; + vec3 LightDirection_cameraspace; + + float Depth; +} vs; + +#ifdef INSTANCED +layout(location = 6) in mat4 Model; +#else +uniform mat4 Model; +#endif +uniform mat4 View; +uniform mat4 Proj; + +uniform vec4 SphereProj; +uniform float Curvature; + +uniform vec3 LightInvDirection_worldspace; +uniform float FogDepth; + +void main(){ + vs.Position_modelspace = Position_modelspace; + +if(DO_CURVATURE) { + if(Curvature > 0) { + vec3 Position_areaspace = Position_modelspace + SphereProj.xyz; + vec2 sph = vec2(acos(Position_areaspace.z / length(Position_areaspace.xyz)), atan(Position_areaspace.y, Position_areaspace.x)); +if(CURV_DEPTH) { + float radius = max(max(abs(Position_areaspace.x), abs(Position_areaspace.y)), abs(Position_areaspace.z)); +} else { + float radius = SphereProj.w; +} + vs.Position_modelspace = mix(vs.Position_modelspace, vec3(sin(sph.x)*cos(sph.y), sin(sph.x)*sin(sph.y), cos(sph.x)) * radius - SphereProj.xyz, Curvature); + } +} + + vec4 Position_cameraspace = View * Model * vec4(vs.Position_modelspace, 1); + gl_Position = Proj * Position_cameraspace; + +if(FOG) { + vs.Depth = length(Position_cameraspace.xyz) / FogDepth; +} + + vs.Texture = Texture_model; + + vs.FaceNormal_modelspace = normalize(Normal_modelspace); +if(PBR) + // TODO: correct normal curvature + vs.FaceNormal_worldspace = normalize((Model * vec4(vs.FaceNormal_modelspace, 0)).xyz); + + // Vector that goes from the vertex to the camera, in camera space. + // In camera space, the camera is at the origin (0,0,0). + vs.EyeDirection_cameraspace = vec3(0,0,0) - Position_cameraspace.xyz; + + // Vector that goes from the vertex to the light, in camera space + vs.LightDirection_cameraspace = (View * vec4(LightInvDirection_worldspace,0)).xyz; +} +} diff --git a/resource/shaders-src/compile.sh b/resource/shaders-src/compile.sh new file mode 100755 index 0000000..1f3618c --- /dev/null +++ b/resource/shaders-src/compile.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +BASEDIR=$(dirname "$0") +TARGETDIR="$BASEDIR/../content/shaders" + +# Tris +glslc -fshader-stage=vertex $BASEDIR/Tris.vs -o $TARGETDIR/Tris.vs.spv +glslc -fshader-stage=fragment $BASEDIR/Tris.fs -o $TARGETDIR/Tris.fs.spv + +# Color +glslc -fshader-stage=vertex $TARGETDIR/Color.vs -o $TARGETDIR/Color.vs.spv +glslc -fshader-stage=fragment $TARGETDIR/Color.fs -o $TARGETDIR/Color.fs.spv + +# Sky +glslc -fshader-stage=vertex $TARGETDIR/Sky.vs -o $TARGETDIR/Sky.vs.spv +glslc -fshader-stage=fragment $TARGETDIR/Sky.fs -o $TARGETDIR/Sky.fs.spv + +# Voxel +glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -o $TARGETDIR/Voxel.vs.spv +glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -o $TARGETDIR/Voxel.fs.spv +glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DINSTANCED -o $TARGETDIR/Voxel.vs.ins.spv +glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DINSTANCED -o $TARGETDIR/Voxel.fs.ins.spv + +glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DGEOMETRY -o $TARGETDIR/Voxel.vs.geo.spv +glslc -fshader-stage=geometry $BASEDIR/Voxel.gs -DGEOMETRY -o $TARGETDIR/Voxel.gs.geo.spv +glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DGEOMETRY -o $TARGETDIR/Voxel.fs.geo.spv +glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.vs.geo.ins.spv +glslc -fshader-stage=geometry $BASEDIR/Voxel.gs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.gs.geo.ins.spv +glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.fs.geo.ins.spv \ No newline at end of file diff --git a/src/client/Client.cpp b/src/client/Client.cpp index f800806..ff5f653 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -1,8 +1,8 @@ #include "Client.hpp" -#include "render/gl/Renderer.hpp" -#include "render/gl/UI.hpp" -#include "render/gl/Pipeline.hpp" +#include "render/index.hpp" +#include "render/UI.hpp" + #include "InputMap.hpp" #include "world/index.hpp" #include @@ -13,7 +13,7 @@ Client::Client(config::client::options& options): options(options) { } Client::~Client() { } void Client::run(server_handle* const localHandle) { - if (!render::gl::Renderer::Load(window, {options.renderer.clear_color})) + if (!render::Load(window, options.preferVulkan, options.renderer)) return; window.setTargetFPS(options.window.targetFPS); @@ -23,7 +23,7 @@ void Client::run(server_handle* const localHandle) { Controllable player(window.getPtr(), inputs, options.control); Camera camera(&player, options.camera); - auto *pipeline = static_cast(render::Renderer::Get()->createPipeline(options.renderer)); + auto pipeline = render::Renderer::Get(); pipeline->LightInvDir = glm::normalize(glm::vec3(-.5f, 2, -2)); render::Renderer::Get()->loadUI(window); @@ -119,7 +119,6 @@ void Client::run(server_handle* const localHandle) { world->setContouring(contouring::load(options.contouring.idx, options.contouring.data)); state.contouring = world->getContouring(); } - pipeline->SkyEnable = options.renderer.skybox; } { // Rendering ZoneScopedN("Render"); @@ -135,8 +134,7 @@ void Client::run(server_handle* const localHandle) { { // Chunks const auto pass = pipeline->beginWorldPass(); const auto draw = [&](glm::mat4 model, buffer::Abstract *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) { - pipeline->SphereProj = glm::vec4(pos, std::get<1>(area)); - pipeline->Curvature = std::get<2>(area); + pipeline->setCurvature(glm::vec4(pos, std::get<1>(area)), std::get<2>(area)); reports.models_count++; reports.tris_count += buffer->draw(pass(model)); }; @@ -160,10 +158,10 @@ void Client::run(server_handle* const localHandle) { } { // Entities const auto pass = pipeline->beginEntityPass(); - const auto draw = [&](const std::vector& models, buffer::Abstract *const buffer) { + /*const auto draw = [&](const std::vector& models, buffer::Abstract *const buffer) { reports.models_count += models.size(); reports.tris_count += buffer->draw(pass(models)); - }; + };*/ //world->getEntitiesModels(draw, frustum, offset, options.voxel_density); } if(state.look_at.has_value()) { // Indicator @@ -178,7 +176,7 @@ void Client::run(server_handle* const localHandle) { { // Swap buffers ZoneScopedN("Swap"); - pipeline->swapBuffer(window.getPtr()); + pipeline->swapBuffer(window); inputs.poll(); FrameMarkNamed("Client"); } @@ -187,7 +185,6 @@ void Client::run(server_handle* const localHandle) { } while (!(inputs.isDown(Input::Quit) || window.shouldClose())); render::UI::Unload(); - delete pipeline; render::Renderer::Unload(); window.destroy(); diff --git a/src/client/Window.cpp b/src/client/Window.cpp index cd36b1d..9fcfca5 100644 --- a/src/client/Window.cpp +++ b/src/client/Window.cpp @@ -42,6 +42,11 @@ bool Window::create(const CreateInfo &opt) { glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); break; + case CreateInfo::Client::Type::VK: + LOG_W("WIP client type"); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + break; + default: FATAL("Unsupported client type"); break; @@ -105,6 +110,7 @@ void Window::waitTargetFPS() { } void Window::destroy() { glfwDestroyWindow(ptr); + ptr = nullptr; } bool Window::shouldClose() { return glfwWindowShouldClose(ptr); } diff --git a/src/client/Window.hpp b/src/client/Window.hpp index c4928a1..cbea211 100644 --- a/src/client/Window.hpp +++ b/src/client/Window.hpp @@ -1,6 +1,7 @@ #pragma once #include "../core/flags.hpp" +#include typedef struct GLFWwindow GLFWwindow; typedef void (*GLFWframebuffersizefun)(GLFWwindow*, int, int); diff --git a/src/client/config.hpp b/src/client/config.hpp index 27e6aec..9fb710f 100644 --- a/src/client/config.hpp +++ b/src/client/config.hpp @@ -5,7 +5,7 @@ #include #include #include "world/Universe.hpp" -#include "render/Pipeline.hpp" +#include "render/Renderer.hpp" #include "contouring/index.hpp" #include "control/Camera.hpp" @@ -31,6 +31,7 @@ public: window.targetFPS = config["window"]["target_fps"].value_or(window.targetFPS); window.samples = config["window"]["samples"].value_or(window.samples); window.fullscreen = config["window"]["fullscreen"].value_or(window.fullscreen); + renderer.inFlightFrames = config["window"]["parallel_frames"].value_or(renderer.inFlightFrames); preferVulkan = config["render"]["prefer_vulkan"].value_or(preferVulkan); renderer.textures = config["render"]["textures"].value_or(renderer.textures); @@ -99,7 +100,8 @@ public: config.insert_or_assign("window", toml::table({ {"target_fps", window.targetFPS}, {"samples", window.samples}, - {"fullscreen", window.fullscreen} + {"fullscreen", window.fullscreen}, + {"parallel_frames", renderer.inFlightFrames} })); config.insert_or_assign("render", toml::table({ {"prefer_vulkan", preferVulkan}, @@ -194,7 +196,7 @@ public: } window; bool preferVulkan = true; - render::Pipeline::options renderer; + render::renderOptions renderer; int culling = 0; struct { @@ -219,6 +221,6 @@ public: int corner = 3; } overlay; - std::optional connection; + std::optional connection = {}; }; } \ No newline at end of file diff --git a/src/client/render/Pipeline.hpp b/src/client/render/Pipeline.hpp deleted file mode 100644 index 81a7879..0000000 --- a/src/client/render/Pipeline.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "gl/pass/VoxelProgram.hpp" - -struct GLFWwindow; -class Camera; - -namespace render { - -/// Handle rendering passes and params -class Pipeline { -public: - /// Rendering options - struct options { - /// Voxel passes - pass::VoxelProgram::options voxel; - /// Display skybox - bool skybox = true; - /// Display only wires - bool wireframe = false; - /// Texture pack name - std::string textures = "1024-realistic"; - /// Textures quality - float mipMapLOD = -.5; - /// Textures anisotropic mapping - int anisotropy = 0; - /// Depth color - glm::vec4 clear_color; - }; - - virtual ~Pipeline() { } - - /// Start new frame and setup - virtual void beginFrame() = 0; - /// Apply postprocessing - virtual void endPass() = 0; - /// Swap displayed image - virtual void swapBuffer(GLFWwindow*) = 0; - - /// Apply camera matrices - virtual void lookFrom(const Camera&) = 0; - - virtual void setClearColor(glm::vec4) = 0; -}; -} \ No newline at end of file diff --git a/src/client/render/Renderer.hpp b/src/client/render/Renderer.hpp index 6223713..fabf8a1 100644 --- a/src/client/render/Renderer.hpp +++ b/src/client/render/Renderer.hpp @@ -1,17 +1,85 @@ #pragma once -#include "../Window.hpp" -#include "Pipeline.hpp" +#include "../../core/flags.hpp" +#include "buffer/Abstract.hpp" #include +#include +#include +#include +#include +class Window; +class Camera; namespace render { +/// Pass options +struct passOptions { + /// Apply light properties + bool pbr = true; + /// Triplanar texture mapping + bool triplanar = false; + /// Transform texture UV + bool stochastic = false; + /// Active geometry pass + bool geometry = false; + /// Blend voxel with mixed materials (requires geometry) + bool blend = true; + /// Depth fog + bool fog = true; + /// Map planets to sphere + bool curvature = true; + /// Keep depth in sphere + bool curv_depth = true; +}; + +/// Rendering options +struct renderOptions { + /// Voxel passes + passOptions voxel; + /// Display skybox + bool skybox = true; + /// Display only wires + bool wireframe = false; + /// Texture pack name + std::string textures = "1024-realistic"; + /// Textures quality + float mipMapLOD = -.5; + /// Textures anisotropic mapping + int anisotropy = 0; + /// Depth color + glm::vec4 clear_color; + /// Parallel processing frames + /// Incease FPS but also a bit latency (Vulkan only) + int inFlightFrames = 2; +}; + /// Rendering plateform interface class Renderer { public: virtual ~Renderer() { } - virtual Pipeline* createPipeline(const Pipeline::options&) = 0; + glm::vec3 LightInvDir = glm::vec3(0.5f, 2, 2); + + /// Start new frame and setup + virtual void beginFrame() = 0; + /// Get started world program + virtual std::function beginWorldPass() = 0; + /// Get started entity program + virtual std::function &)> beginEntityPass() = 0; + /// Draw cube indicator + virtual size_t drawIndicatorCube(glm::mat4 model) = 0; + /// Apply postprocessing + virtual void endPass() = 0; + /// Swap displayed image + virtual void swapBuffer(Window &) = 0; + + /// Apply camera matrices + virtual void lookFrom(const Camera &) = 0; + + virtual void setClearColor(glm::vec4) = 0; + virtual void setCurvature(glm::vec4, float) = 0; + virtual void reloadShaders(const passOptions &) = 0; + virtual void reloadTextures(const std::string &, float mipMapLOD, float anisotropy) = 0; virtual void loadUI(Window&) = 0; diff --git a/src/client/render/buffer/Abstract.hpp b/src/client/render/buffer/Abstract.hpp new file mode 100644 index 0000000..b719e15 --- /dev/null +++ b/src/client/render/buffer/Abstract.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace buffer { + /// Draw options + struct params { + /// Bind only vertices positions + bool vertexOnly; + /// Draw instanced model + size_t instances = 1; + }; +} diff --git a/src/client/render/gl/Pipeline.cpp b/src/client/render/gl/Pipeline.cpp deleted file mode 100644 index 06f0bb0..0000000 --- a/src/client/render/gl/Pipeline.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "Pipeline.hpp" - -#include "../../../core/world/materials.hpp" -#include "../../control/Camera.hpp" -#include - -using namespace render::gl; - -Pipeline::Pipeline(const Pipeline::options& options): - IndicatorCubeBuffer(GL_LINES, 24, { - glm::vec3(0, 0, 0), glm::vec3(0, 0, 1), - glm::vec3(0, 0, 1), glm::vec3(0, 1, 1), - glm::vec3(0, 1, 1), glm::vec3(0, 1, 0), - glm::vec3(0, 1, 0), glm::vec3(0, 0, 0), - glm::vec3(1, 0, 0), glm::vec3(1, 0, 1), - glm::vec3(1, 0, 1), glm::vec3(1, 1, 1), - glm::vec3(1, 1, 1), glm::vec3(1, 1, 0), - glm::vec3(1, 1, 0), glm::vec3(1, 0, 0), - glm::vec3(0, 0, 0), glm::vec3(1, 0, 0), - glm::vec3(0, 0, 1), glm::vec3(1, 0, 1), - glm::vec3(0, 1, 1), glm::vec3(1, 1, 1), - glm::vec3(0, 1, 0), glm::vec3(1, 1, 0), - }, { - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), - }) { - glGenVertexArrays(1, &VertexArrayID); - glBindVertexArray(VertexArrayID); - - reloadShaders(options.voxel); - SkyPass = std::make_unique(); - SkyEnable = options.skybox; - IndicatorPass = std::make_unique(); - - FogColor = glm::vec3(options.clear_color.x, options.clear_color.y, options.clear_color.z); - loadTextures(options.textures, options.mipMapLOD, options.anisotropy); -} - -Pipeline::~Pipeline() { - unloadTextures(); - glDeleteVertexArrays(1, &VertexArrayID); -} - -void Pipeline::beginFrame() { - TracyGpuZone("Render"); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -std::function Pipeline::beginWorldPass() { - WorldPass->useIt(); - WorldPass->start(this); - return [&](glm::mat4 model) { - return WorldPass->setup(this, model); - }; -} - -std::function &)> Pipeline::beginEntityPass() { - EntityPass->useIt(); - EntityPass->start(this); - return [&](const std::vector& models) { - return EntityPass->setup(this, models); - }; -} - -size_t Pipeline::drawIndicatorCube(glm::mat4 model) { - IndicatorPass->useIt(); - return IndicatorCubeBuffer.draw(IndicatorPass->setup(this, model)); -} - -void Pipeline::endPass() { - if(SkyEnable) { - SkyPass->draw(this); - } -} - -void Pipeline::swapBuffer(GLFWwindow* ptr) { - TracyGpuZone("Swap"); - glfwSwapBuffers(ptr); - TracyGpuCollect; -} - -void Pipeline::reloadShaders(const pass::VoxelProgram::options& options) { - WorldPass = std::make_unique(options); - EntityPass = std::make_unique(options); -} -void Pipeline::reloadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) { - unloadTextures(); - loadTextures(texturePath, mipMapLOD, anisotropy); -} - -void Pipeline::unloadTextures() { - glDeleteTextures(1, &HOSAtlas); - glDeleteTextures(1, &NormalAtlas); - glDeleteTextures(1, &TextureAtlas); -} -void Pipeline::loadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) { - std::vector terrainTextures; - for(const auto& texture: world::materials::textures) { - terrainTextures.emplace_back(texturePath + "/terrain/" + texture); - } - const auto ani = anisotropy >= 1 ? (1 << (static_cast(anisotropy)-1)) : 0; - TextureAtlas = pass::Program::loadTextureArray(terrainTextures, "", mipMapLOD, ani); - NormalAtlas = pass::Program::loadTextureArray(terrainTextures, ".nrm", mipMapLOD, ani); - HOSAtlas = pass::Program::loadTextureArray(terrainTextures, ".hos", mipMapLOD, ani); - - Skybox = pass::Program::loadTextureCube(texturePath + "/sky/Space_tray"); -} - -void Pipeline::lookFrom(const Camera& camera) { - ProjectionMatrix = camera.getProjectionMatrix(); - ViewMatrix = camera.getViewMatrix(); - FogDepth = camera.getDepth(); -} - -void Pipeline::setClearColor(glm::vec4 c) { - FogColor = c; - glClearColor(c.r, c.g, c.b, c.a); -} \ No newline at end of file diff --git a/src/client/render/gl/Pipeline.hpp b/src/client/render/gl/Pipeline.hpp deleted file mode 100644 index c3590c2..0000000 --- a/src/client/render/gl/Pipeline.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include "../Pipeline.hpp" -#include -#include -#include -#include "pass/WorldProgram.hpp" -#include "pass/EntityProgram.hpp" -#include "pass/SkyProgram.hpp" -#include "pass/ColorProgram.hpp" -#include "buffer/Colored.hpp" - -namespace render::gl { - -/// Handle OpenGL rendering passes and params -class Pipeline final: public render::Pipeline { -public: - Pipeline(const options&); - Pipeline(Pipeline &&) = delete; - Pipeline(const Pipeline &) = delete; - Pipeline &operator=(Pipeline &&) = delete; - Pipeline &operator=(const Pipeline &) = delete; - ~Pipeline(); - - glm::vec3 LightInvDir = glm::vec3(0.5f, 2, 2); - glm::vec3 FogColor; - GLfloat FogDepth; - - /// Sphere bending - /// offset.xyz radius.w - glm::vec4 SphereProj; - /// Ratio between spherical and cartesian - float Curvature; - - bool SkyEnable; - - glm::mat4 getProjectionMatrix() const { - return ProjectionMatrix; - } - glm::mat4 getViewMatrix() const { - return ViewMatrix; - } - - GLuint getTextureAtlas() const { - return TextureAtlas; - } - GLuint getNormalAtlas() const { - return NormalAtlas; - } - GLuint getHOSAtlas() const { - return HOSAtlas; - } - GLuint getSkyTexture() const { - return Skybox; - } - - /// Start new frame and setup - void beginFrame() override; - /// Get started world program - std::function beginWorldPass(); - /// Get started entity program - std::function&)> beginEntityPass(); - /// Draw cube indicator - size_t drawIndicatorCube(glm::mat4 model); - /// Apply postprocessing - void endPass() override; - void swapBuffer(GLFWwindow *) override; - - void setClearColor(glm::vec4) override; - - /// Apply camera matrices - void lookFrom(const Camera&) override; - void reloadShaders(const pass::VoxelProgram::options &); - void reloadTextures(const std::string &, float mipMapLOD, float anisotropy); - -private: - GLuint VertexArrayID; - - std::unique_ptr WorldPass; - std::unique_ptr EntityPass; - std::unique_ptr SkyPass; - std::unique_ptr IndicatorPass; - buffer::Colored IndicatorCubeBuffer; - - glm::mat4 ProjectionMatrix; - glm::mat4 ViewMatrix; - - GLuint TextureAtlas; - GLuint NormalAtlas; - GLuint HOSAtlas; - GLuint Skybox; - - void loadTextures(const std::string &, float mipMapLOD, float anisotropy); - void unloadTextures(); -}; - -} \ No newline at end of file diff --git a/src/client/render/gl/Renderer.cpp b/src/client/render/gl/Renderer.cpp index 286b893..a373248 100644 --- a/src/client/render/gl/Renderer.cpp +++ b/src/client/render/gl/Renderer.cpp @@ -1,7 +1,9 @@ #include "Renderer.hpp" #include "UI.hpp" -#include "Pipeline.hpp" +#include "../../../core/world/materials.hpp" +#include "../../control/Camera.hpp" +#include "../../Window.hpp" #include "../../../core/utils/logger.hpp" #include @@ -12,11 +14,59 @@ using namespace render::gl; constexpr auto GL_MAJOR = 4; constexpr auto GL_MINOR = 6; -Renderer::Renderer(const Options& options): options(options) { } -Renderer::~Renderer() { } +Renderer::Renderer(const renderOptions& options): + IndicatorCubeBuffer(GL_LINES, 24, { + glm::vec3(0, 0, 0), glm::vec3(0, 0, 1), + glm::vec3(0, 0, 1), glm::vec3(0, 1, 1), + glm::vec3(0, 1, 1), glm::vec3(0, 1, 0), + glm::vec3(0, 1, 0), glm::vec3(0, 0, 0), + glm::vec3(1, 0, 0), glm::vec3(1, 0, 1), + glm::vec3(1, 0, 1), glm::vec3(1, 1, 1), + glm::vec3(1, 1, 1), glm::vec3(1, 1, 0), + glm::vec3(1, 1, 0), glm::vec3(1, 0, 0), + glm::vec3(0, 0, 0), glm::vec3(1, 0, 0), + glm::vec3(0, 0, 1), glm::vec3(1, 0, 1), + glm::vec3(0, 1, 1), glm::vec3(1, 1, 1), + glm::vec3(0, 1, 0), glm::vec3(1, 1, 0), + }, { + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1), + }) { + glGenVertexArrays(1, &VertexArrayID); + glBindVertexArray(VertexArrayID); -bool Renderer::Load(Window& window, const Options& options) { - auto windowInfo = GetWindowInfo(); + reloadShaders(options.voxel); + SkyPass = std::make_unique(); + SkyEnable = options.skybox; + IndicatorPass = std::make_unique(); + + FogColor = glm::vec3(options.clear_color.x, options.clear_color.y, options.clear_color.z); + loadTextures(options.textures, options.mipMapLOD, options.anisotropy); +} + +Renderer::~Renderer() { + unloadTextures(); + glDeleteVertexArrays(1, &VertexArrayID); +} + +void framebuffer_size_callback(GLFWwindow *, int width, int height) { + glViewport(0, 0, width, height); +} + +bool Renderer::Load(Window& window, const renderOptions& opt) { + Window::CreateInfo windowInfo; + windowInfo.pfnResize = framebuffer_size_callback; + windowInfo.client = {Window::CreateInfo::Client::Type::GL, GL_MAJOR, GL_MINOR}; windowInfo.samples = 1; if (!window.create(windowInfo)) return false; @@ -30,21 +80,7 @@ bool Renderer::Load(Window& window, const Options& options) { return false; } LOG_D("OpenGL " << glGetString(GL_VERSION) << ", GLSL " << glGetString(GL_SHADING_LANGUAGE_VERSION)); - sInstance = new Renderer(options); - return true; -} -void framebuffer_size_callback(GLFWwindow *, int width, int height) { - glViewport(0, 0, width, height); -} -Window::CreateInfo Renderer::GetWindowInfo() { - Window::CreateInfo windowInfo; - windowInfo.pfnResize = framebuffer_size_callback; - windowInfo.client = { Window::CreateInfo::Client::Type::GL, GL_MAJOR, GL_MINOR }; - return windowInfo; -} - -render::Pipeline *Renderer::createPipeline(const render::Pipeline::options &opt) { // Enable depth test glEnable(GL_DEPTH_TEST); // Accept fragment if it closer to the camera than the former one @@ -60,12 +96,95 @@ render::Pipeline *Renderer::createPipeline(const render::Pipeline::options &opt) glEnable(GL_MULTISAMPLE); } - glClearColor(options.clear_color.x, options.clear_color.y, options.clear_color.z, options.clear_color.w); + glClearColor(opt.clear_color.x, opt.clear_color.y, opt.clear_color.z, opt.clear_color.w); TracyGpuContext; - return new Pipeline(opt); + + sInstance = new Renderer(opt); + return true; } void Renderer::loadUI(Window& w) { UI::Load(w); +} + + +void Renderer::beginFrame() { + TracyGpuZone("Render"); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +std::function Renderer::beginWorldPass() { + WorldPass->useIt(); + WorldPass->start(this); + return [&](glm::mat4 model) { + return WorldPass->setup(this, model); + }; +} + +std::function &)> Renderer::beginEntityPass() { + EntityPass->useIt(); + EntityPass->start(this); + return [&](const std::vector& models) { + return EntityPass->setup(this, models); + }; +} + +size_t Renderer::drawIndicatorCube(glm::mat4 model) { + IndicatorPass->useIt(); + return IndicatorCubeBuffer.draw(IndicatorPass->setup(this, model)); +} + +void Renderer::endPass() { + if(SkyEnable) { + SkyPass->draw(this); + } +} + +void Renderer::swapBuffer(Window& w) { + TracyGpuZone("Swap"); + glfwSwapBuffers(w.getPtr()); + TracyGpuCollect; +} + +void Renderer::reloadShaders(const pass::VoxelProgram::options& options) { + WorldPass = std::make_unique(options); + EntityPass = std::make_unique(options); +} +void Renderer::reloadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) { + unloadTextures(); + loadTextures(texturePath, mipMapLOD, anisotropy); +} + +void Renderer::unloadTextures() { + glDeleteTextures(1, &HOSAtlas); + glDeleteTextures(1, &NormalAtlas); + glDeleteTextures(1, &TextureAtlas); +} +void Renderer::loadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) { + std::vector terrainTextures; + for(const auto& texture: world::materials::textures) { + terrainTextures.emplace_back(texturePath + "/terrain/" + texture); + } + const auto ani = anisotropy >= 1 ? (1 << (static_cast(anisotropy)-1)) : 0; + TextureAtlas = pass::Program::loadTextureArray(terrainTextures, "", mipMapLOD, ani); + NormalAtlas = pass::Program::loadTextureArray(terrainTextures, ".nrm", mipMapLOD, ani); + HOSAtlas = pass::Program::loadTextureArray(terrainTextures, ".hos", mipMapLOD, ani); + + Skybox = pass::Program::loadTextureCube(texturePath + "/sky/Space_tray"); +} + +void Renderer::lookFrom(const Camera& camera) { + ProjectionMatrix = camera.getProjectionMatrix(); + ViewMatrix = camera.getViewMatrix(); + FogDepth = camera.getDepth(); +} + +void Renderer::setClearColor(glm::vec4 c) { + FogColor = c; + glClearColor(c.r, c.g, c.b, c.a); +} +void Renderer::setCurvature(glm::vec4 sp, float c) { + SphereProj = sp; + Curvature = c; } \ No newline at end of file diff --git a/src/client/render/gl/Renderer.hpp b/src/client/render/gl/Renderer.hpp index f04a7e4..a3a3efd 100644 --- a/src/client/render/gl/Renderer.hpp +++ b/src/client/render/gl/Renderer.hpp @@ -1,7 +1,12 @@ #pragma once #include "../Renderer.hpp" - -#include +#include +#include +#include "pass/WorldProgram.hpp" +#include "pass/EntityProgram.hpp" +#include "pass/SkyProgram.hpp" +#include "pass/ColorProgram.hpp" +#include "buffer/Colored.hpp" namespace render::gl { @@ -10,23 +15,85 @@ class Renderer final: public render::Renderer { public: virtual ~Renderer(); - struct Options { - /// Depth color - glm::vec4 clear_color; - }; + static bool Load(Window& window, const renderOptions& options); - static bool Load(Window& window, const Options &options); + glm::vec3 FogColor; + GLfloat FogDepth; - render::Pipeline *createPipeline(const render::Pipeline::options &) override; + glm::mat4 getProjectionMatrix() const { + return ProjectionMatrix; + } + glm::mat4 getViewMatrix() const { + return ViewMatrix; + } + + GLuint getTextureAtlas() const { + return TextureAtlas; + } + GLuint getNormalAtlas() const { + return NormalAtlas; + } + GLuint getHOSAtlas() const { + return HOSAtlas; + } + GLuint getSkyTexture() const { + return Skybox; + } + + void beginFrame() override; + std::function beginWorldPass() override; + std::function&)> beginEntityPass() override; + size_t drawIndicatorCube(glm::mat4 model) override; + void endPass() override; + void swapBuffer(Window&) override; + + void setClearColor(glm::vec4) override; + void setCurvature(glm::vec4, float) override; + glm::vec4 getSphereProj() const { + return SphereProj; + } + float getCurvature() const { + return Curvature; + } + + /// Apply camera matrices + void lookFrom(const Camera&) override; + void reloadShaders(const pass::VoxelProgram::options &) override; + void reloadTextures(const std::string &, float mipMapLOD, float anisotropy) override; void loadUI(Window &) override; static _FORCE_INLINE_ Renderer *Get() { return static_cast(render::Renderer::Get()); } private: - Renderer(const Options &options); - static Window::CreateInfo GetWindowInfo(); + Renderer(const renderOptions &options); - Options options; + GLuint VertexArrayID; + + std::unique_ptr WorldPass; + std::unique_ptr EntityPass; + std::unique_ptr SkyPass; + std::unique_ptr IndicatorPass; + buffer::Colored IndicatorCubeBuffer; + + glm::mat4 ProjectionMatrix; + glm::mat4 ViewMatrix; + + GLuint TextureAtlas; + GLuint NormalAtlas; + GLuint HOSAtlas; + GLuint Skybox; + + /// Sphere bending + /// offset.xyz radius.w + glm::vec4 SphereProj; + /// Ratio between spherical and cartesian + float Curvature; + + /// Draw skybox + bool SkyEnable; + + void loadTextures(const std::string &, float mipMapLOD, float anisotropy); + void unloadTextures(); }; } \ No newline at end of file diff --git a/src/client/render/gl/UI.cpp b/src/client/render/gl/UI.cpp index a8619ca..c0069f0 100644 --- a/src/client/render/gl/UI.cpp +++ b/src/client/render/gl/UI.cpp @@ -1,5 +1,6 @@ #include "UI.hpp" +#include "../../Window.hpp" #include #include #include diff --git a/src/client/render/gl/UI.hpp b/src/client/render/gl/UI.hpp index b3f494a..c67b48e 100644 --- a/src/client/render/gl/UI.hpp +++ b/src/client/render/gl/UI.hpp @@ -1,7 +1,8 @@ #pragma once #include "../UI.hpp" -#include "../../Window.hpp" +struct GLFWwindow; +class Window; namespace render::gl { diff --git a/src/client/render/gl/buffer/Abstract.hpp b/src/client/render/gl/buffer/Abstract.hpp index df51899..5282395 100644 --- a/src/client/render/gl/buffer/Abstract.hpp +++ b/src/client/render/gl/buffer/Abstract.hpp @@ -1,17 +1,8 @@ #pragma once -#include -#include +#include "../../buffer/Abstract.hpp" namespace buffer { - /// Draw options - struct params { - /// Bind only vertices positions - bool vertexOnly; - /// Draw instanced model - size_t instances = 1; - }; - /// Abstract OpenGL Buffer class Abstract { public: diff --git a/src/client/render/gl/pass/ColorProgram.cpp b/src/client/render/gl/pass/ColorProgram.cpp index 8b24596..1dc6b21 100644 --- a/src/client/render/gl/pass/ColorProgram.cpp +++ b/src/client/render/gl/pass/ColorProgram.cpp @@ -1,6 +1,6 @@ #include "ColorProgram.hpp" -#include "../Pipeline.hpp" +#include "../Renderer.hpp" using namespace pass; @@ -20,7 +20,7 @@ ColorProgram::~ColorProgram() { } std::string ColorProgram::getName() const { return "Color"; } -buffer::params ColorProgram::setup(render::gl::Pipeline *renderer, glm::mat4 modelMatrix) { +buffer::params ColorProgram::setup(render::gl::Renderer *renderer, glm::mat4 modelMatrix) { const auto mvp = renderer->getProjectionMatrix() * renderer->getViewMatrix() * modelMatrix; setMVP(&mvp[0][0]); return buffer::params{.vertexOnly = false}; diff --git a/src/client/render/gl/pass/ColorProgram.hpp b/src/client/render/gl/pass/ColorProgram.hpp index 5c383f8..e6cde47 100644 --- a/src/client/render/gl/pass/ColorProgram.hpp +++ b/src/client/render/gl/pass/ColorProgram.hpp @@ -9,7 +9,7 @@ namespace pass { ColorProgram(); ~ColorProgram(); - buffer::params setup(render::gl::Pipeline *, glm::mat4 modelMatrix); + buffer::params setup(render::gl::Renderer *, glm::mat4 modelMatrix); protected: std::string getName() const override; diff --git a/src/client/render/gl/pass/EntityProgram.cpp b/src/client/render/gl/pass/EntityProgram.cpp index 2fd185b..9c72d40 100644 --- a/src/client/render/gl/pass/EntityProgram.cpp +++ b/src/client/render/gl/pass/EntityProgram.cpp @@ -1,6 +1,6 @@ #include "EntityProgram.hpp" -#include "../Pipeline.hpp" +#include "../Renderer.hpp" using namespace pass; @@ -17,7 +17,7 @@ EntityProgram::EntityProgram(const EntityProgram::options &opts) : VoxelProgram( } EntityProgram::~EntityProgram() { } -buffer::params EntityProgram::setup(render::gl::Pipeline *renderer, const std::vector& modelsMatrices) { +buffer::params EntityProgram::setup(render::gl::Renderer *renderer, const std::vector& modelsMatrices) { setModels(&modelsMatrices[0][0][0], modelsMatrices.size()); auto params = VoxelProgram::setup(renderer); params.instances = modelsMatrices.size(); diff --git a/src/client/render/gl/pass/EntityProgram.hpp b/src/client/render/gl/pass/EntityProgram.hpp index 59a5270..0018de6 100644 --- a/src/client/render/gl/pass/EntityProgram.hpp +++ b/src/client/render/gl/pass/EntityProgram.hpp @@ -11,7 +11,7 @@ namespace pass { static constexpr auto LOCATION = 6; - buffer::params setup(render::gl::Pipeline *, const std::vector &modelsMatrices); + buffer::params setup(render::gl::Renderer *, const std::vector &modelsMatrices); void disable(); protected: diff --git a/src/client/render/gl/pass/Program.hpp b/src/client/render/gl/pass/Program.hpp index 8c647fe..36d1a62 100644 --- a/src/client/render/gl/pass/Program.hpp +++ b/src/client/render/gl/pass/Program.hpp @@ -8,8 +8,11 @@ #include "Shader.hpp" #include "../buffer/Abstract.hpp" +namespace render { + struct passOptions; +} namespace render::gl { - class Pipeline; + class Renderer; } /// OpenGL programs namespace pass { diff --git a/src/client/render/gl/pass/SkyProgram.cpp b/src/client/render/gl/pass/SkyProgram.cpp index 7918d28..7f7fd33 100644 --- a/src/client/render/gl/pass/SkyProgram.cpp +++ b/src/client/render/gl/pass/SkyProgram.cpp @@ -1,6 +1,6 @@ #include "SkyProgram.hpp" -#include "../Pipeline.hpp" +#include "../Renderer.hpp" using namespace pass; @@ -65,7 +65,7 @@ SkyProgram::~SkyProgram() { } std::string SkyProgram::getName() const { return "Sky"; } -void SkyProgram::start(render::gl::Pipeline *renderer) { +void SkyProgram::start(render::gl::Renderer *renderer) { const auto fixedView = glm::mat4(glm::mat3(renderer->getViewMatrix())); setView(&fixedView[0][0]); setProjection(&renderer->getProjectionMatrix()[0][0]); @@ -75,7 +75,7 @@ void SkyProgram::start(render::gl::Pipeline *renderer) { buffer::params SkyProgram::setup() { return buffer::params{.vertexOnly = true}; } -void SkyProgram::draw(render::gl::Pipeline *renderer) { +void SkyProgram::draw(render::gl::Renderer *renderer) { useIt(); start(renderer); CubeBuffer.draw(setup()); diff --git a/src/client/render/gl/pass/SkyProgram.hpp b/src/client/render/gl/pass/SkyProgram.hpp index 7e7c6b7..5ef0a43 100644 --- a/src/client/render/gl/pass/SkyProgram.hpp +++ b/src/client/render/gl/pass/SkyProgram.hpp @@ -10,11 +10,11 @@ namespace pass { SkyProgram(); ~SkyProgram(); - void start(render::gl::Pipeline *); + void start(render::gl::Renderer *); buffer::params setup(); /// Direct draw using internal buffer - void draw(render::gl::Pipeline *); + void draw(render::gl::Renderer *); protected: std::string getName() const override; diff --git a/src/client/render/gl/pass/VoxelProgram.cpp b/src/client/render/gl/pass/VoxelProgram.cpp index f46d4b9..c34942d 100644 --- a/src/client/render/gl/pass/VoxelProgram.cpp +++ b/src/client/render/gl/pass/VoxelProgram.cpp @@ -1,6 +1,6 @@ #include "VoxelProgram.hpp" -#include "../Pipeline.hpp" +#include "../Renderer.hpp" using namespace pass; @@ -53,7 +53,7 @@ VoxelProgram::~VoxelProgram() { } std::string VoxelProgram::getName() const { return "Voxel"; } -void VoxelProgram::start(render::gl::Pipeline *renderer) { +void VoxelProgram::start(render::gl::Renderer *renderer) { bindTexture(renderer->getTextureAtlas()); bindNormal(renderer->getNormalAtlas()); bindHOS(renderer->getHOSAtlas()); @@ -62,9 +62,9 @@ void VoxelProgram::start(render::gl::Pipeline *renderer) { setView(&renderer->getViewMatrix()[0][0]); setProj(&renderer->getProjectionMatrix()[0][0]); } -buffer::params VoxelProgram::setup(render::gl::Pipeline *renderer) { - setSphereProj(&renderer->SphereProj[0]); - setCurvature(renderer->Curvature); +buffer::params VoxelProgram::setup(render::gl::Renderer *renderer) { + setSphereProj(&renderer->getSphereProj()[0]); + setCurvature(renderer->getCurvature()); return buffer::params{.vertexOnly = false}; } diff --git a/src/client/render/gl/pass/VoxelProgram.hpp b/src/client/render/gl/pass/VoxelProgram.hpp index bf76ff7..a5daf7e 100644 --- a/src/client/render/gl/pass/VoxelProgram.hpp +++ b/src/client/render/gl/pass/VoxelProgram.hpp @@ -6,33 +6,15 @@ namespace pass { /// Abstract voxels pass class VoxelProgram: public Program { public: - /// Pass options - struct options { - /// Apply light properties - bool pbr = true; - /// Triplanar texture mapping - bool triplanar = false; - /// Transform texture UV - bool stochastic = false; - /// Active geometry pass - bool geometry = false; - /// Blend voxel with mixed materials (requires geometry) - bool blend = true; - /// Depth fog - bool fog = true; - /// Map planets to sphere - bool curvature = true; - /// Keep depth in sphere - bool curv_depth = true; - }; + using options = render::passOptions; VoxelProgram(const options &opts, std::vector flags = {}); ~VoxelProgram(); - void start(render::gl::Pipeline *); + void start(render::gl::Renderer *); protected: - buffer::params setup(render::gl::Pipeline *); + buffer::params setup(render::gl::Renderer *); std::string getName() const override; void setView(const GLfloat *matrix); diff --git a/src/client/render/gl/pass/WorldProgram.cpp b/src/client/render/gl/pass/WorldProgram.cpp index 6b7b84e..a33abcc 100644 --- a/src/client/render/gl/pass/WorldProgram.cpp +++ b/src/client/render/gl/pass/WorldProgram.cpp @@ -1,6 +1,6 @@ #include "WorldProgram.hpp" -#include "../Pipeline.hpp" +#include "../Renderer.hpp" using namespace pass; @@ -11,7 +11,7 @@ WorldProgram::WorldProgram(const WorldProgram::options& opts): VoxelProgram(opts WorldProgram::~WorldProgram() { } -buffer::params WorldProgram::setup(render::gl::Pipeline *renderer, glm::mat4 modelMatrix) { +buffer::params WorldProgram::setup(render::gl::Renderer *renderer, glm::mat4 modelMatrix) { setModel(&modelMatrix[0][0]); return VoxelProgram::setup(renderer); } diff --git a/src/client/render/gl/pass/WorldProgram.hpp b/src/client/render/gl/pass/WorldProgram.hpp index dc310ef..eeede0a 100644 --- a/src/client/render/gl/pass/WorldProgram.hpp +++ b/src/client/render/gl/pass/WorldProgram.hpp @@ -9,7 +9,7 @@ namespace pass { WorldProgram(const options &opts); ~WorldProgram(); - buffer::params setup(render::gl::Pipeline *, glm::mat4 modelMatrix); + buffer::params setup(render::gl::Renderer *, glm::mat4 modelMatrix); protected: void setModel(const GLfloat *matrix); diff --git a/src/client/render/index.cpp b/src/client/render/index.cpp new file mode 100644 index 0000000..54f1906 --- /dev/null +++ b/src/client/render/index.cpp @@ -0,0 +1,28 @@ +#include "gl/Renderer.hpp" +#include "vk/Renderer.hpp" +#include "../../core/utils/logger.hpp" +#include "../Window.hpp" + +namespace render { + +bool Load(Window& window, bool preferVulkan, const renderOptions& options) { + if(!preferVulkan) { + LOG_D("Trying OpenGL"); + if(gl::Renderer::Load(window, options)) + return true; + window.destroy(); + } + LOG_D("Trying Vulkan"); + if(vk::Renderer::Load(window, options)) + return true; + window.destroy(); + if(preferVulkan) { + LOG_I("Fallback to OpenGL"); + if(gl::Renderer::Load(window, options)) + return true; + } + LOG_E("No available graphics library"); + return false; +} + +} \ No newline at end of file diff --git a/src/client/render/index.hpp b/src/client/render/index.hpp new file mode 100644 index 0000000..5184394 --- /dev/null +++ b/src/client/render/index.hpp @@ -0,0 +1,8 @@ +#pragma once + +class Window; +namespace render { +struct renderOptions; + +bool Load(Window& window, bool preferVulkan, const renderOptions& options); +} \ No newline at end of file diff --git a/src/client/render/vk/CommandPool.cpp b/src/client/render/vk/CommandPool.cpp new file mode 100644 index 0000000..3218b3f --- /dev/null +++ b/src/client/render/vk/CommandPool.cpp @@ -0,0 +1,129 @@ +#include "CommandPool.hpp" + +#include "shared.hpp" +#include "../Renderer.hpp" + +using namespace render::vk; + +CommandPool::CommandPool(VkDevice device, const std::vector& views, PipelineRef pipe, const PhysicalDeviceInfo& info, const renderOptions& opt): + device(device) { + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = info.queueIndices.graphicsFamily.value(); + poolInfo.flags = 0; // Optional + + if (vkCreateCommandPool(device, &poolInfo, ALLOC, &graphicsPool) != VK_SUCCESS) { + FATAL("Failed to create command pool!"); + } + + allocate(views, pipe, info.swapDetails.capabilities.currentExtent, opt); +} +CommandPool::~CommandPool() { + if(!freed) + free(); + + vkDestroyCommandPool(device, graphicsPool, ALLOC); +} +void CommandPool::allocate(const std::vector& views, PipelineRef pipe, VkExtent2D extent, const renderOptions& opt) { + assert(freed); + + framebuffers.resize(views.size()); + + for (size_t i = 0; i < views.size(); i++) { + VkImageView attachments[] = { views[i] }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = pipe.first; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, ALLOC, &framebuffers[i]) != VK_SUCCESS) { + FATAL("Failed to create framebuffer!"); + } + } + + graphicsBuffers.resize(framebuffers.size()); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = graphicsPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) graphicsBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, graphicsBuffers.data()) != VK_SUCCESS) { + FATAL("Failed to allocate command buffers!"); + } + + for (size_t i = 0; i < graphicsBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; + beginInfo.pInheritanceInfo = nullptr; + + if (vkBeginCommandBuffer(graphicsBuffers[i], &beginInfo) != VK_SUCCESS) { + FATAL("Failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = pipe.first; + renderPassInfo.framebuffer = framebuffers[i]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = extent; + + VkClearValue clearColor = {opt.clear_color.x, opt.clear_color.y, opt.clear_color.z, opt.clear_color.a}; + renderPassInfo.clearValueCount = 1; //TODO: clear depth + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(graphicsBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(graphicsBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.second); + vkCmdDraw(graphicsBuffers[i], 3, 1, 0, 0); + vkCmdEndRenderPass(graphicsBuffers[i]); + + if (vkEndCommandBuffer(graphicsBuffers[i]) != VK_SUCCESS) { + FATAL("Failed to record command buffer!"); + } + } + + freed = false; +} +void CommandPool::free() { + assert(!freed); + for (size_t i = 0; i < framebuffers.size(); i++) { + vkDestroyFramebuffer(device, framebuffers[i], nullptr); + } + + vkFreeCommandBuffers(device, graphicsPool, static_cast(graphicsBuffers.size()), graphicsBuffers.data()); + + freed = true; +} + +void CommandPool::submitGraphics(uint32_t idx, VkQueue graphicsQueue, VkSemaphore waitSemaphore, VkSemaphore signalSemaphore, VkFence submittedFence) { + assert(!freed); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {waitSemaphore}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &graphicsBuffers[idx]; + + VkSemaphore signalSemaphores[] = {signalSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1, &submittedFence); + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, submittedFence) != VK_SUCCESS) { + FATAL("Failed to submit draw command buffer!"); + } +} \ No newline at end of file diff --git a/src/client/render/vk/CommandPool.hpp b/src/client/render/vk/CommandPool.hpp new file mode 100644 index 0000000..34a9d4c --- /dev/null +++ b/src/client/render/vk/CommandPool.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "forward.hpp" +#include + +namespace render::vk { +class SwapChain; +class Pipeline; + +class CommandPool { +public: + CommandPool(VkDevice, const std::vector&, PipelineRef, const PhysicalDeviceInfo&, const renderOptions&); + ~CommandPool(); + + void submitGraphics(uint32_t, VkQueue, VkSemaphore, VkSemaphore, VkFence); + + void allocate(const std::vector&, PipelineRef, VkExtent2D, const renderOptions&); + void free(); + +private: + VkDevice device; + + std::vector framebuffers; + + VkCommandPool graphicsPool; + std::vector graphicsBuffers; + bool freed = true; +}; +} \ No newline at end of file diff --git a/src/client/render/vk/Pipeline.cpp b/src/client/render/vk/Pipeline.cpp new file mode 100644 index 0000000..9b3ac41 --- /dev/null +++ b/src/client/render/vk/Pipeline.cpp @@ -0,0 +1,238 @@ +#include "Pipeline.hpp" + +#include "shared.hpp" +#include "../../../core/data/file.hpp" +#include "../Renderer.hpp" + +#define CONTENT_DIR "content/" +#define SHADER_DIR CONTENT_DIR "shaders/" +#define TEXTURES_DIR CONTENT_DIR "textures/" + +constexpr auto BLENDING = true; + +using namespace render::vk; + +Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const renderOptions &options): device(device) { + { // Render pass + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = info.surfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + //TODO: subpasses (world, entities, colors, sky, ui) + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; //TODO: layouts bind + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, ALLOC, &renderPass) != VK_SUCCESS) { + FATAL("Failed to create render pass!"); + } + } + { + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; // TODO: setup layouts + pipelineLayoutInfo.pSetLayouts = nullptr; + pipelineLayoutInfo.pushConstantRangeCount = 0; + pipelineLayoutInfo.pPushConstantRanges = nullptr; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, ALLOC, &pipelineLayout) != VK_SUCCESS) { + FATAL("Failed to create pipeline layout!"); + } + } + { // Pipeline + { // Modules + data::file_content vsFile({SHADER_DIR "Tris.vs.spv"}); + data::file_content fsFile({SHADER_DIR "Tris.fs.spv"}); + + auto createShaderModule = [&](const data::file_content &code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, ALLOC, &shaderModule) != VK_SUCCESS) { + FATAL("Failed to create shader module!"); + } + return shaderModule; + }; + + vsShader = createShaderModule(vsFile); + fsShader = createShaderModule(fsFile); + } + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vsShader; + vertShaderStageInfo.pName = "main"; + vertShaderStageInfo.pSpecializationInfo = nullptr; //TODO: pass constants + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fsShader; + fragShaderStageInfo.pName = "main"; + fragShaderStageInfo.pSpecializationInfo = nullptr; //TODO: pass constants + + //TODO: geometry + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; // TODO: uniforms, and buffers + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; // TODO: uniforms, and buffers + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + // Viewport + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)info.swapDetails.capabilities.currentExtent.width; + viewport.height = (float)info.swapDetails.capabilities.currentExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = info.swapDetails.capabilities.currentExtent; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = options.wireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + VkPipelineMultisampleStateCreateInfo multisampling{}; //TODO: multisampling NOTE: requires gpu feature + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.stencilTestEnable = VK_FALSE; + depthStencil.depthTestEnable = VK_FALSE; // TODO: depth filter NOTE: set as null if create info + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + if constexpr (BLENDING) { + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + } else { + colorBlendAttachment.blendEnable = VK_FALSE; + } + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.attachmentCount = 1; //MAYBE: deferred multitarget + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + /* MAYBE: dynamic state + VkDynamicState dynamicStates[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH}; + + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = 2; + dynamicState.pDynamicStates = dynamicStates; + */ + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; //FIXME: depthTest + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = nullptr; + + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = -1; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, ALLOC, &graphicsPipeline) != VK_SUCCESS) { + FATAL("Failed to create graphics pipeline!"); + } + } +} +Pipeline::~Pipeline() { + vkDestroyPipeline(device, graphicsPipeline, ALLOC); + vkDestroyPipelineLayout(device, pipelineLayout, ALLOC); + vkDestroyRenderPass(device, renderPass, ALLOC); + + vkDestroyShaderModule(device, fsShader, ALLOC); + vkDestroyShaderModule(device, vsShader, ALLOC); +} \ No newline at end of file diff --git a/src/client/render/vk/Pipeline.hpp b/src/client/render/vk/Pipeline.hpp new file mode 100644 index 0000000..602bf18 --- /dev/null +++ b/src/client/render/vk/Pipeline.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "forward.hpp" + +namespace render::vk { + +class Pipeline { +public: + Pipeline(VkDevice, const PhysicalDeviceInfo&, const renderOptions&); + ~Pipeline(); + + PipelineRef getRef() { return std::make_pair(renderPass, graphicsPipeline); } + +private: + VkDevice device; + + VkShaderModule vsShader; + VkShaderModule fsShader; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; +}; + +} \ No newline at end of file diff --git a/src/client/render/vk/Renderer.cpp b/src/client/render/vk/Renderer.cpp new file mode 100644 index 0000000..7bd2350 --- /dev/null +++ b/src/client/render/vk/Renderer.cpp @@ -0,0 +1,476 @@ +#include "Renderer.hpp" + +#include "UI.hpp" +#include "../../Window.hpp" +#include "shared.hpp" +#include "SwapChain.hpp" +#include "Pipeline.hpp" +#include "CommandPool.hpp" +#include +#include +#include +#include + +using namespace render::vk; + +constexpr auto LOAD_DEVICE = true; +constexpr auto VALIDATION_LAYER = true; + +void set_current_extent(VkSurfaceCapabilitiesKHR &capabilities, GLFWwindow *ptr); + +Renderer::Renderer(VkInstance instance, VkDevice device, const PhysicalDeviceInfo& info, const renderOptions& opt): + options(opt), instance(instance), surface(info.surface), device(device), + physicalInfo(std::make_unique(info)) { + vkGetDeviceQueue(device, info.queueIndices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, info.queueIndices.presentFamily.value(), 0, &presentQueue); + + set_current_extent(physicalInfo->swapDetails.capabilities, physicalInfo->window); + physicalInfo->surfaceFormat = [&]() { + for(const auto& format: info.swapDetails.formats) { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + + LOG_W("Using suboptimal surface format"); + return info.swapDetails.formats[0]; + }(); + + swapChain = std::make_unique(device, *physicalInfo.get()); + pipeline = std::make_unique(device, *physicalInfo.get(), options); + commandPool = std::make_unique(device, swapChain->getImageViews(), pipeline->getRef(), *physicalInfo.get(), options); + + { + imageAvailableSemaphores.resize(opt.inFlightFrames); + renderFinishedSemaphores.resize(opt.inFlightFrames); + inFlightFences.resize(opt.inFlightFrames); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (int i = 0; i < opt.inFlightFrames; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, ALLOC, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, ALLOC, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, ALLOC, &inFlightFences[i]) != VK_SUCCESS) + { + FATAL("Failed to create synchronization objects!"); + } + } + vkResetFences(device, 1, &inFlightFences[currentFrame]); + } +} +Renderer::~Renderer() { + vkDeviceWaitIdle(device); + destroySwapChain(); + + for(size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroyFence(device, inFlightFences[i], ALLOC); + vkDestroySemaphore(device, renderFinishedSemaphores[i], ALLOC); + vkDestroySemaphore(device, imageAvailableSemaphores[i], ALLOC); + } + + commandPool.reset(); + + vkDestroyDevice(device, ALLOC); + vkDestroySurfaceKHR(instance, surface, ALLOC); + vkDestroyInstance(instance, ALLOC); +} + +void Renderer::recreateSwapChain() { + LOG_D("Recreating swapchain"); + vkDeviceWaitIdle(device); + destroySwapChain(); + + physicalInfo->swapDetails = SwapChainSupportDetails::Query(physicalInfo->device, physicalInfo->surface); + set_current_extent(physicalInfo->swapDetails.capabilities, physicalInfo->window); + swapChain = std::make_unique(device, *physicalInfo.get()); + pipeline = std::make_unique(device, *physicalInfo.get(), options); + commandPool->allocate(swapChain->getImageViews(), pipeline->getRef(), physicalInfo->swapDetails.capabilities.currentExtent, options); +} +void Renderer::destroySwapChain() { + commandPool->free(); + pipeline.reset(); + swapChain.reset(); +} + +void on_resize_callback(GLFWwindow *, int, int) { + Renderer::Get()->setResized(); +} +void set_current_extent(VkSurfaceCapabilitiesKHR &capabilities, GLFWwindow* ptr) { + if(capabilities.currentExtent.width != INT32_MAX) { + return; + } + auto windowSize = std::make_pair(0, 0); + glfwGetFramebufferSize(ptr, &windowSize.first, &windowSize.second); + capabilities.currentExtent = VkExtent2D{ + std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, windowSize.first)), + std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, windowSize.second))}; +}; + +bool Renderer::Load(Window& window, const renderOptions& opt) { + Window::CreateInfo windowInfo; + windowInfo.pfnResize = on_resize_callback; + windowInfo.client = {Window::CreateInfo::Client::Type::VK, 0, 0}; + windowInfo.samples = 1; + if (!window.create(windowInfo)) + return false; + + if (volkInitialize() != VK_SUCCESS) { + LOG_E("Failed to initialize Vulkan"); + return false; + } + + VkInstance instance; + std::vector layers; + { + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Univerxel"; + appInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 1); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_2; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + std::vector extensions; + { // Check extensions + uint32_t availableExtensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &availableExtensionCount, nullptr); + std::vector availableExtensions(availableExtensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &availableExtensionCount, availableExtensions.data()); + +#if DEBUG + LOG_D("Available extensions:"); + for (const auto &extension : availableExtensions) { + LOG_D('\t' << extension.extensionName << " : " << extension.specVersion); + } +#endif + + uint32_t glfwExtensionCount = 0; + const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + extensions.reserve(glfwExtensionCount); + for (uint32_t i = 0; i < glfwExtensionCount; i++) { + if (std::none_of(availableExtensions.begin(), availableExtensions.end(), [&](const VkExtensionProperties &ex) { return strcmp(ex.extensionName, glfwExtensions[i]) == 0; })) { + LOG_E("Missing required glfw extension " << glfwExtensions[i]); + return false; + } + extensions.push_back(glfwExtensions[i]); + } + + } + createInfo.enabledExtensionCount = extensions.size(); + createInfo.ppEnabledExtensionNames = extensions.data(); + + { // Check layers + uint32_t availableLayerCount = 0; + vkEnumerateInstanceLayerProperties(&availableLayerCount, nullptr); + std::vector availableLayers(availableLayerCount); + vkEnumerateInstanceLayerProperties(&availableLayerCount, availableLayers.data()); + +#if DEBUG + LOG_D("Available layers:"); + for (const auto &layer : availableLayers) { + LOG_D('\t' << layer.layerName << " : " << layer.specVersion); + } +#endif + + const auto hasLayer = [&availableLayers](const char *layer) { + return std::any_of(availableLayers.begin(), availableLayers.end(), [&layer](const VkLayerProperties &l) { return strcmp(l.layerName, layer) == 0; }); + }; + + if constexpr (VALIDATION_LAYER) { + constexpr auto VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; + if (hasLayer(VALIDATION_LAYER_NAME)) { + layers.push_back(VALIDATION_LAYER_NAME); + } else { + LOG_W("Validation layer unavailable"); + } + if (hasLayer(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + layers.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } else { + LOG_W("Debug utils layer unavailable"); + } + } + } + createInfo.enabledLayerCount = layers.size(); + createInfo.ppEnabledLayerNames = layers.data(); + + if (vkCreateInstance(&createInfo, ALLOC, &instance) != VK_SUCCESS) { + LOG_E("Failed to create Vulkan instance"); + return false; + } + } + + if constexpr(LOAD_DEVICE) { + volkLoadInstanceOnly(instance); + } else { + volkLoadInstance(instance); + } + + const auto version = volkGetInstanceVersion(); + LOG_D("Vulkan " << VK_VERSION_MAJOR(version) << '.' << VK_VERSION_MINOR(version) << '.' << VK_VERSION_PATCH(version) << ", GLSL TODO:"); + + VkSurfaceKHR surface; + if (glfwCreateWindowSurface(instance, window.getPtr(), ALLOC, &surface) != VK_SUCCESS) { + LOG_E("Failed to create window surface!"); + return false; + } + + PhysicalDeviceInfo physicalInfo; + std::vector requiredExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + if (deviceCount == 0) { + LOG_E("Any GPU with Vulkan support"); + return false; + } + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + uint bestScore = 0; + for(const auto& device: devices) { + uint score = 1; + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + if (!deviceFeatures.geometryShader) + continue; + + { + uint32_t availableExtensionsCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &availableExtensionsCount, nullptr); + std::vector availableExtensions(availableExtensionsCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &availableExtensionsCount, availableExtensions.data()); + + if (std::any_of(requiredExtensions.begin(), requiredExtensions.end(), [&](const char *required) { + return std::none_of(availableExtensions.begin(), availableExtensions.end(), [&](const VkExtensionProperties &ex) { + return strcmp(ex.extensionName, required) == 0; + }); + })) + continue; + } + + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + score += 10000; + + score += deviceProperties.limits.maxImageDimension2D; + //TODO: check others limits + + auto infos = PhysicalDeviceInfo{device, window.getPtr(), + SwapChainSupportDetails::Query(device, surface), QueueFamilyIndices::Query(device, surface), + VkSurfaceFormatKHR(), surface}; + + if (!infos.queueIndices.isComplete()) + continue; + + if (!infos.swapDetails.isValid()) + continue; + + if (score > bestScore) { + bestScore = score; + physicalInfo = infos; + } + } + if (physicalInfo.device == VK_NULL_HANDLE) { + LOG_E("Any GPU matching requirements"); + return false; + } + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(physicalInfo.device, &deviceProperties); + LOG_D("Using " << deviceProperties.deviceName); + } + + VkDevice device; + { + std::vector queueCreateInfos; + { + std::set uniqueQueueFamilies = {physicalInfo.queueIndices.graphicsFamily.value(), physicalInfo.queueIndices.presentFamily.value()}; + + const float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) + { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + //TODO: + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = queueCreateInfos.size(); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledLayerCount = layers.size(); + createInfo.ppEnabledLayerNames = layers.data(); + createInfo.enabledExtensionCount = requiredExtensions.size(); + createInfo.ppEnabledExtensionNames = requiredExtensions.data(); + + if (vkCreateDevice(physicalInfo.device, &createInfo, ALLOC, &device) != VK_SUCCESS) { + LOG_E("Failed to bind graphic device"); + return false; + } + } + + if constexpr(LOAD_DEVICE) { + volkLoadDevice(device); + } + + sInstance = new Renderer(instance, device, physicalInfo, opt); + return true; +} + +void Renderer::loadUI(Window& w) { + UI::Load(w); +} + +SwapChainSupportDetails SwapChainSupportDetails::Query(VkPhysicalDevice device, VkSurfaceKHR surface) { + SwapChainSupportDetails swapDetails; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &swapDetails.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + if (formatCount != 0) { + swapDetails.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, swapDetails.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + if (presentModeCount != 0) { + swapDetails.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, swapDetails.presentModes.data()); + } + return swapDetails; +} + +QueueFamilyIndices QueueFamilyIndices::Query(VkPhysicalDevice device, VkSurfaceKHR surface) { + QueueFamilyIndices queueIndices; + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for(const auto& queueFamily: queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + queueIndices.graphicsFamily = i; + } + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (presentSupport) { + queueIndices.presentFamily = i; + } + +#if DEBUG + LOG_D("Queue " << i << ' ' << (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ? "graphics " : "") + << (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ? "compute " : "") + << (queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ? "transfer " : "") + << (queueFamily.queueFlags & VK_QUEUE_PROTECTED_BIT ? "protected " : "") + << 'x' << queueFamily.queueCount); +#endif + i++; + } + + return queueIndices; +} + +void Renderer::beginFrame() { + assert(currentImage == UINT32_MAX); + + //FIXME: TracyGpuZone("Render"); + if (auto newImage = swapChain->acquireNextImage(imageAvailableSemaphores[currentFrame], inFlightFences[currentFrame])) { + currentImage = newImage.value(); + } else { + recreateSwapChain(); + beginFrame(); + } +} + +std::function Renderer::beginWorldPass() { + assert(currentImage < swapChain->getImageViews().size()); + + commandPool->submitGraphics(currentImage, graphicsQueue, imageAvailableSemaphores[currentFrame], + renderFinishedSemaphores[currentFrame], inFlightFences[currentFrame]); + + /*WorldPass->useIt(); + WorldPass->start(this);*/ + return [&](glm::mat4) { + return buffer::params{}; //WorldPass->setup(this, model); + }; +} + +std::function &)> Renderer::beginEntityPass() { + /*EntityPass->useIt(); + EntityPass->start(this);*/ + return [&](const std::vector&) { + return buffer::params{}; //EntityPass->setup(this, models); + }; +} + +size_t Renderer::drawIndicatorCube(glm::mat4) { + /*IndicatorPass->useIt(); + return IndicatorCubeBuffer.draw(IndicatorPass->setup(this, model));*/ + return 0; +} + +void Renderer::endPass() { + /*if(SkyEnable) { + SkyPass->draw(this); + }*/ +} + +void Renderer::swapBuffer(Window&) { + /*TracyGpuZone("Swap"); + glfwSwapBuffers(ptr); + TracyGpuCollect;*/ + + if(!swapChain->presentImage(currentImage, presentQueue, renderFinishedSemaphores[currentFrame]) || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + + currentFrame = (currentFrame + 1) % renderFinishedSemaphores.size(); + currentImage = UINT32_MAX; + + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); +} + +void Renderer::reloadShaders(const passOptions&) { + /*WorldPass = std::make_unique(options); + EntityPass = std::make_unique(options);*/ +} +void Renderer::reloadTextures(const std::string&, float, float) { + /*unloadTextures(); + loadTextures(texturePath, mipMapLOD, anisotropy);*/ +} + +void Renderer::lookFrom(const Camera&) { + /*ProjectionMatrix = camera.getProjectionMatrix(); + ViewMatrix = camera.getViewMatrix(); + FogDepth = camera.getDepth();*/ +} + +void Renderer::setClearColor(glm::vec4) { + /*FogColor = c; + glClearColor(c.r, c.g, c.b, c.a);*/ +} +void Renderer::setCurvature(glm::vec4, float) { + /*SphereProj = sp; + Curvature = c;*/ +} \ No newline at end of file diff --git a/src/client/render/vk/Renderer.hpp b/src/client/render/vk/Renderer.hpp new file mode 100644 index 0000000..8f517e5 --- /dev/null +++ b/src/client/render/vk/Renderer.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "../Renderer.hpp" +#include "forward.hpp" + +namespace render::vk { +class SwapChain; +class Pipeline; +class CommandPool; + +/// Vulkan rendering +class Renderer final: public render::Renderer { +public: + virtual ~Renderer(); + + static bool Load(Window& window, const renderOptions& options); + + void loadUI(Window &) override; + + static _FORCE_INLINE_ Renderer *Get() { return static_cast(render::Renderer::Get()); } + + void beginFrame() override; + std::function beginWorldPass() override; + std::function &)> beginEntityPass() override; + size_t drawIndicatorCube(glm::mat4 model) override; + void endPass() override; + void swapBuffer(Window&) override; + + void setClearColor(glm::vec4) override; + void setCurvature(glm::vec4, float) override; + + /// Apply camera matrices + void lookFrom(const Camera &) override; + void reloadShaders(const passOptions &) override; + void reloadTextures(const std::string &, float mipMapLOD, float anisotropy) override; + + void setResized() { framebufferResized = true; } + +private: + Renderer(VkInstance, VkDevice, const PhysicalDeviceInfo &, const renderOptions &); + + renderOptions options; + + VkInstance instance; + VkSurfaceKHR surface; + VkDevice device; + VkQueue graphicsQueue; + VkQueue presentQueue; + + std::unique_ptr physicalInfo; + std::unique_ptr swapChain; + std::unique_ptr pipeline; + std::unique_ptr commandPool; + + size_t currentFrame = 0; + uint32_t currentImage = UINT32_MAX; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + + bool framebufferResized = false; + void recreateSwapChain(); + void destroySwapChain(); +}; +} \ No newline at end of file diff --git a/src/client/render/vk/SwapChain.cpp b/src/client/render/vk/SwapChain.cpp new file mode 100644 index 0000000..0feb3a5 --- /dev/null +++ b/src/client/render/vk/SwapChain.cpp @@ -0,0 +1,136 @@ +#include "SwapChain.hpp" + +#include "shared.hpp" + +using namespace render::vk; + +SwapChain::SwapChain(VkDevice device, const PhysicalDeviceInfo& info): device(device) { + { // Swapchain + VkPresentModeKHR presentMode = [&]() { + // MAYBE: add prefer no triple buffering options + for (const auto& availablePresentMode: info.swapDetails.presentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + }(); + + uint32_t imageCount = info.swapDetails.capabilities.minImageCount + 1; + if (info.swapDetails.capabilities.maxImageCount > 0 && imageCount > info.swapDetails.capabilities.maxImageCount) { + imageCount = info.swapDetails.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = info.surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = info.surfaceFormat.format; + createInfo.imageColorSpace = info.surfaceFormat.colorSpace; + createInfo.imageExtent = info.swapDetails.capabilities.currentExtent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; //VK_IMAGE_USAGE_TRANSFER_DST_BIT + + if (info.queueIndices.graphicsFamily != info.queueIndices.presentFamily) { + uint32_t queueFamilyIndices[] = {info.queueIndices.graphicsFamily.value(), info.queueIndices.presentFamily.value()}; + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; + createInfo.pQueueFamilyIndices = nullptr; + } + createInfo.preTransform = info.swapDetails.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = chain; + + if (vkCreateSwapchainKHR(device, &createInfo, ALLOC, &chain) != VK_SUCCESS) { + FATAL("Failed to create swap chain!"); + } + } + + { // Images + uint32_t imageCount; + vkGetSwapchainImagesKHR(device, chain, &imageCount, nullptr); + images.resize(imageCount); + vkGetSwapchainImagesKHR(device, chain, &imageCount, images.data()); + + imageViews.resize(images.size()); + for (size_t i = 0; i < images.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = images[i]; + + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = info.surfaceFormat.format; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, ALLOC, &imageViews[i]) != VK_SUCCESS) { + FATAL("Failed to create image views!"); + } + } + } + + imagesInFlight.resize(imageViews.size(), VK_NULL_HANDLE); +} +SwapChain::~SwapChain() { + for (auto imageView: imageViews) { + vkDestroyImageView(device, imageView, ALLOC); + } + vkDestroySwapchainKHR(device, chain, ALLOC); +} + +std::optional SwapChain::acquireNextImage(VkSemaphore semaphore, VkFence fence) { + uint32_t imageIndex; + auto result = vkAcquireNextImageKHR(device, chain, UINT64_MAX, semaphore, VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) + return {}; + + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + FATAL("Failed to acquire swap chain image!"); + } + + // Check if a previous frame is using this image (i.e. there is its fence to wait on) + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); + } + // Mark the image as now being in use by this frame + imagesInFlight[imageIndex] = fence; + return imageIndex; +} +bool SwapChain::presentImage(uint32_t idx, VkQueue queue, VkSemaphore signalSemaphore) { + VkSemaphore signalSemaphores[] = {signalSemaphore}; + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {chain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &idx; + presentInfo.pResults = nullptr; + + auto result = vkQueuePresentKHR(queue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + return false; + } else if (result != VK_SUCCESS) { + FATAL("Failed to present swap chain image!"); + } + return true; +} \ No newline at end of file diff --git a/src/client/render/vk/SwapChain.hpp b/src/client/render/vk/SwapChain.hpp new file mode 100644 index 0000000..c740c1f --- /dev/null +++ b/src/client/render/vk/SwapChain.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "forward.hpp" +#include +#include + +namespace render::vk { + +class SwapChain { +public: + SwapChain(VkDevice, const PhysicalDeviceInfo &); + ~SwapChain(); + + const std::vector &getImageViews() { return imageViews; } + + std::optional acquireNextImage(VkSemaphore, VkFence); + bool presentImage(uint32_t, VkQueue, VkSemaphore); + +private: + VkDevice device; + + VkSwapchainKHR chain; + std::vector images; + std::vector imageViews; + std::vector imagesInFlight; +}; + +} \ No newline at end of file diff --git a/src/client/render/vk/UI.cpp b/src/client/render/vk/UI.cpp new file mode 100644 index 0000000..a41ffe7 --- /dev/null +++ b/src/client/render/vk/UI.cpp @@ -0,0 +1,30 @@ +#include "UI.hpp" + +#include "../../Window.hpp" +#include + +using namespace render::vk; + +UI::UI(GLFWwindow *window): render::UI() { + // Setup Platform/Renderer bindings + /*ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + aim = Texture::CreatePtr("ui/Aim", false);*/ +} +UI::~UI() { + /*ImGui_ImplGlfw_Shutdown(); + ImGui_ImplOpenGL3_Shutdown();*/ +} + +UI::Actions UI::draw(config::client::options &o, state::state &s, const state::reports &r) { + /*ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame();*/ + return UI::Actions::None; //render::UI::draw(o, s, r, aim); +} +void UI::render() { + //ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +void UI::Load(Window& w) { + UI::sInstance = new UI(w.getPtr()); +} \ No newline at end of file diff --git a/src/client/render/vk/UI.hpp b/src/client/render/vk/UI.hpp new file mode 100644 index 0000000..bb0ceea --- /dev/null +++ b/src/client/render/vk/UI.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../UI.hpp" +struct GLFWwindow; +class Window; + +namespace render::vk { + +/// ImGui Vulkan/GLFW interface +class UI final: public render::UI { +private: + UI(GLFWwindow *window); +public: + ~UI(); + + static void Load(Window &); + + Actions draw(config::client::options &, state::state &, const state::reports &) override; + void render() override; + +private: + intptr_t aim; +}; +} \ No newline at end of file diff --git a/src/client/render/vk/forward.hpp b/src/client/render/vk/forward.hpp new file mode 100644 index 0000000..f66bf15 --- /dev/null +++ b/src/client/render/vk/forward.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +namespace render { struct renderOptions; } +namespace render::vk { + struct PhysicalDeviceInfo; + using PipelineRef = std::pair; + constexpr VkAllocationCallbacks *ALLOC = nullptr; +} \ No newline at end of file diff --git a/src/client/render/vk/shared.hpp b/src/client/render/vk/shared.hpp new file mode 100644 index 0000000..a546981 --- /dev/null +++ b/src/client/render/vk/shared.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "forward.hpp" +#include "../../../core/utils/logger.hpp" +#include +#include + +struct GLFWwindow; +namespace render::vk { +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + static SwapChainSupportDetails Query(VkPhysicalDevice, VkSurfaceKHR); + bool isValid() const { return !formats.empty() && !presentModes.empty(); } +}; +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + static QueueFamilyIndices Query(VkPhysicalDevice, VkSurfaceKHR); + bool isComplete() const { return graphicsFamily.has_value() && presentFamily.has_value(); } +}; +struct PhysicalDeviceInfo { + VkPhysicalDevice device = VK_NULL_HANDLE; + GLFWwindow *window; + SwapChainSupportDetails swapDetails; + QueueFamilyIndices queueIndices; + VkSurfaceFormatKHR surfaceFormat; + VkSurfaceKHR surface; +}; +} \ No newline at end of file diff --git a/src/client/world/index.cpp b/src/client/world/index.cpp index f6629f3..f65f697 100644 --- a/src/client/world/index.cpp +++ b/src/client/world/index.cpp @@ -11,6 +11,7 @@ namespace world::client { return std::make_unique(ct.value(), distOpts); #ifndef STANDALONE } else if(localHandle != nullptr) { + LOG_D("Using local universe"); return std::make_unique(localHandle); #endif } else { diff --git a/src/core/config.hpp b/src/core/config.hpp index e9636e8..b615091 100644 --- a/src/core/config.hpp +++ b/src/core/config.hpp @@ -4,6 +4,8 @@ #include #include #include +#include "../server/config.hpp" +#include "../client/config.hpp" namespace config { @@ -21,8 +23,7 @@ public: LOG_E("Config file " << path << " not found. Creating default"); return toml::table(); }(); - if (config["server"]["enabled"].value_or(false)) - { + if (config["server"]["enabled"].value_or(false)) { _server = server::options(config["server"]); } if(config["client"]["enabled"].value_or(false)) { diff --git a/src/core/net/Client.hpp b/src/core/net/Client.hpp index d118e49..08126a6 100644 --- a/src/core/net/Client.hpp +++ b/src/core/net/Client.hpp @@ -15,7 +15,7 @@ public: if(auto addr = ct.toAddress()) return addr.value(); - FATAL("Invalid ip server address format"); + FATAL("Invalid server address format"); }(); host = enet_host_create(NULL, 1, CHANNEL_COUNT, 0, 0);