diff --git a/App/Src/Main.cpp b/App/Src/Main.cpp index deeb249..ff35718 100644 --- a/App/Src/Main.cpp +++ b/App/Src/Main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,16 @@ namespace auto quad = Tkge::Graphics::Quad{}; quad.Create(glm::vec2{400.0f}); quad.transform.position.x = -250.0f; - quad.tint = kvf::magenta_v; + + auto colourBitmap = kvf::ColorBitmap{glm::ivec2{2, 2}}; + colourBitmap[0, 0] = kvf::red_v; + colourBitmap[1, 0] = kvf::green_v; + colourBitmap[0, 1] = kvf::blue_v; + colourBitmap[1, 1] = kvf::magenta_v; + auto texture = Tkge::Graphics::Texture{&engine.RenderDevice()}; + texture.Create(colourBitmap.bitmap()); + texture.sampler.filter = vk::Filter::eNearest; + quad.texture = &texture; auto instancedQuad = Tkge::Graphics::InstancedQuad{}; instancedQuad.Create(glm::vec2{150.0f}); diff --git a/Assets/Shaders/Default.frag b/Assets/Shaders/Default.frag index c7dfc5c..1539d01 100644 Binary files a/Assets/Shaders/Default.frag and b/Assets/Shaders/Default.frag differ diff --git a/Assets/Shaders/Default.vert b/Assets/Shaders/Default.vert index 5910629..99ed1c4 100644 Binary files a/Assets/Shaders/Default.vert and b/Assets/Shaders/Default.vert differ diff --git a/Lib/Include/Tkge/Engine.hpp b/Lib/Include/Tkge/Engine.hpp index 9444b4e..7e1251b 100644 --- a/Lib/Include/Tkge/Engine.hpp +++ b/Lib/Include/Tkge/Engine.hpp @@ -25,6 +25,7 @@ namespace Tkge explicit Engine(const WindowSurface& surface = {}, vk::SampleCountFlagBits aa = AntiAliasing); [[nodiscard]] const kvf::RenderDevice& RenderDevice() const { return _renderDevice; } + [[nodiscard]] kvf::RenderDevice& RenderDevice() { return _renderDevice; } [[nodiscard]] glm::ivec2 FramebufferSize() const; [[nodiscard]] auto FramebufferFormat() const -> vk::Format { return _renderPass.get_color_format(); } diff --git a/Lib/Include/Tkge/Graphics/Drawable.hpp b/Lib/Include/Tkge/Graphics/Drawable.hpp index ba2a037..2852d47 100644 --- a/Lib/Include/Tkge/Graphics/Drawable.hpp +++ b/Lib/Include/Tkge/Graphics/Drawable.hpp @@ -24,8 +24,11 @@ namespace Tkge::Graphics .vertices = this->GetVertices(), .indices = this->GetIndices(), .topology = this->GetTopology(), + .texture = texture, }; } + + const Texture* texture{nullptr}; }; template TGeometry> diff --git a/Lib/Include/Tkge/Graphics/Primitive.hpp b/Lib/Include/Tkge/Graphics/Primitive.hpp index 8939619..9a747a4 100644 --- a/Lib/Include/Tkge/Graphics/Primitive.hpp +++ b/Lib/Include/Tkge/Graphics/Primitive.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -10,5 +11,6 @@ namespace Tkge::Graphics std::span indices{}; vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList}; + const Texture* texture{nullptr}; }; } // namespace Tkge::Graphics diff --git a/Lib/Include/Tkge/Graphics/Renderer.hpp b/Lib/Include/Tkge/Graphics/Renderer.hpp index 5a1841d..9c137c8 100644 --- a/Lib/Include/Tkge/Graphics/Renderer.hpp +++ b/Lib/Include/Tkge/Graphics/Renderer.hpp @@ -41,7 +41,7 @@ namespace Tkge::Graphics }; void UpdateInstances(std::span instances); - [[nodiscard]] bool WriteSets() const; + [[nodiscard]] bool WriteSets(const Texture* texture) const; void BindVboAndDraw(const Primitive& primitive, std::uint32_t instances) const; kvf::RenderPass* _renderPass{}; diff --git a/Lib/Include/Tkge/Graphics/ResourcePool.hpp b/Lib/Include/Tkge/Graphics/ResourcePool.hpp index 7b7659d..9311744 100644 --- a/Lib/Include/Tkge/Graphics/ResourcePool.hpp +++ b/Lib/Include/Tkge/Graphics/ResourcePool.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include namespace Tkge::Graphics @@ -21,5 +22,8 @@ namespace Tkge::Graphics /// \brief Allocate a Buffer for given usage and of given size. [[nodiscard]] virtual Buffer& AllocateBuffer(vk::BufferUsageFlags usage, vk::DeviceSize size) = 0; + + [[nodiscard]] virtual vk::Sampler GetSampler(const Graphics::TextureSampler& sampler) = 0; + [[nodiscard]] virtual const Graphics::Texture& GetFallbackTexture() const = 0; }; } // namespace Tkge::Graphics diff --git a/Lib/Include/Tkge/Graphics/Texture.hpp b/Lib/Include/Tkge/Graphics/Texture.hpp new file mode 100644 index 0000000..8b92a70 --- /dev/null +++ b/Lib/Include/Tkge/Graphics/Texture.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace Tkge::Graphics +{ + class Texture + { + public: + using Sampler = TextureSampler; + + explicit Texture(gsl::not_null renderDevice); + + bool Create(const kvf::Bitmap& bitmap); + bool Decompress(std::span compressedImage); + + [[nodiscard]] const kvf::vma::Image& GetImage() const { return _image; } + [[nodiscard]] glm::ivec2 GetSize() const; + + Sampler sampler{}; + + private: + kvf::vma::Image _image{}; + }; +} // namespace Tkge::Graphics diff --git a/Lib/Include/Tkge/Graphics/TextureSampler.hpp b/Lib/Include/Tkge/Graphics/TextureSampler.hpp new file mode 100644 index 0000000..129342f --- /dev/null +++ b/Lib/Include/Tkge/Graphics/TextureSampler.hpp @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace Tkge::Graphics +{ + struct TextureSampler + { + vk::SamplerAddressMode wrap{vk::SamplerAddressMode::eRepeat}; + vk::Filter filter{vk::Filter::eLinear}; + vk::SamplerMipmapMode mipMap{vk::SamplerMipmapMode::eNearest}; + vk::BorderColor borderColour{vk::BorderColor::eFloatTransparentBlack}; + + bool operator==(const TextureSampler&) const = default; + }; +} // namespace Tkge::Graphics diff --git a/Lib/Src/Detail/PipelinePool.hpp b/Lib/Src/Detail/PipelinePool.hpp index 6908fff..66af8eb 100644 --- a/Lib/Src/Detail/PipelinePool.hpp +++ b/Lib/Src/Detail/PipelinePool.hpp @@ -75,12 +75,13 @@ namespace Tkge::Detail { static constexpr auto StageFlags = vk::ShaderStageFlagBits::eAllGraphics; // set 0: builtin - auto set0 = std::array{}; + auto set0 = std::array{}; // set 0, binding 0: view set0[0].setBinding(0).setDescriptorCount(1).setDescriptorType(vk::DescriptorType::eUniformBuffer).setStageFlags(StageFlags); // set 0, binding 1: instances set0[1].setBinding(1).setDescriptorCount(1).setDescriptorType(vk::DescriptorType::eStorageBuffer).setStageFlags(StageFlags); - // TODO: texture bindings + // set0, binding 2: texture + set0[2].setBinding(2).setDescriptorCount(1).setDescriptorType(vk::DescriptorType::eCombinedImageSampler).setStageFlags(StageFlags); // TODO: set 1: user data diff --git a/Lib/Src/Detail/SamplerPool.hpp b/Lib/Src/Detail/SamplerPool.hpp new file mode 100644 index 0000000..b2b16e6 --- /dev/null +++ b/Lib/Src/Detail/SamplerPool.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace Tkge::Detail +{ + class SamplerPool + { + public: + explicit SamplerPool(gsl::not_null renderDevice) : _renderDevice(renderDevice) {} + + [[nodiscard]] vk::Sampler GetSampler(const Graphics::TextureSampler& in) + { + const auto key = GetHash(in); + auto it = _samplers.find(key); + if (it != _samplers.end()) { return *it->second; } + + auto sci = _renderDevice->sampler_info(in.wrap, in.filter); + sci.setMipmapMode(in.mipMap).setBorderColor(in.borderColour); + it = _samplers.insert({key, _renderDevice->get_device().createSamplerUnique(sci)}).first; + return *it->second; + } + + private: + [[nodiscard]] static std::size_t GetHash(const Graphics::TextureSampler& sampler) + { + return klib::make_combined_hash(sampler.wrap, sampler.filter, sampler.mipMap, sampler.borderColour); + } + + gsl::not_null _renderDevice; + + std::unordered_map _samplers{}; + }; +} // namespace Tkge::Detail diff --git a/Lib/Src/Engine.cpp b/Lib/Src/Engine.cpp index fc28f1e..1e995c0 100644 --- a/Lib/Src/Engine.cpp +++ b/Lib/Src/Engine.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,12 +11,22 @@ namespace Tkge { namespace { + struct PixelBitmap + { + [[nodiscard]] constexpr kvf::Bitmap ToBitmap() const { return kvf::Bitmap{.bytes = bytes, .size = {1, 1}}; } + + std::array bytes{}; + }; + + constexpr auto WhiteBitmap = std::bit_cast(kvf::white_v); + class ResourcePool : public Graphics::IResourcePool { public: explicit ResourcePool(gsl::not_null renderDevice, vk::SampleCountFlagBits framebufferSamples) - : _pipelinePool(renderDevice, framebufferSamples), _bufferPool(renderDevice) + : _pipelinePool(renderDevice, framebufferSamples), _bufferPool(renderDevice), _samplerPool(renderDevice), _whiteTexture(renderDevice) { + _whiteTexture.Create(WhiteBitmap.ToBitmap()); } [[nodiscard]] vk::PipelineLayout PipelineLayout() const final { return _pipelinePool.PipelineLayout(); } @@ -32,11 +43,16 @@ namespace Tkge return _bufferPool.Allocate(usage, size); } + [[nodiscard]] vk::Sampler GetSampler(const Graphics::TextureSampler& sampler) final { return _samplerPool.GetSampler(sampler); } + [[nodiscard]] const Graphics::Texture& GetFallbackTexture() const final { return _whiteTexture; } + void NextFrame() { _bufferPool.NextFrame(); } private: Detail::PipelinePool _pipelinePool; Detail::BufferPool _bufferPool; + Detail::SamplerPool _samplerPool; + Graphics::Texture _whiteTexture; }; } // namespace diff --git a/Lib/Src/Glsl/Default.frag b/Lib/Src/Glsl/Default.frag index 2881601..a9e0c05 100644 --- a/Lib/Src/Glsl/Default.frag +++ b/Lib/Src/Glsl/Default.frag @@ -1,10 +1,13 @@ #version 450 core +layout (set = 0, binding = 2) uniform sampler2D tex; + layout (location = 0) in vec4 inColour; +layout (location = 1) in vec2 inUv; layout (location = 0) out vec4 outColour; void main() { - outColour = inColour; + outColour = inColour * texture(tex, inUv); } diff --git a/Lib/Src/Glsl/Default.vert b/Lib/Src/Glsl/Default.vert index fce546a..ab94e9b 100644 --- a/Lib/Src/Glsl/Default.vert +++ b/Lib/Src/Glsl/Default.vert @@ -21,6 +21,7 @@ layout (set = 0, binding = 1) readonly buffer Instances }; layout (location = 0) out vec4 outColour; +layout (location = 1) out vec2 outUv; void main() { @@ -29,6 +30,7 @@ void main() const vec4 worldPos = instance.model * vec4(aPos, 0.0, 1.0); outColour = aColour * instance.tint; + outUv = aUv; gl_Position = matVP * worldPos; } diff --git a/Lib/Src/Graphics/Renderer.cpp b/Lib/Src/Graphics/Renderer.cpp index 08f11e3..14082a3 100644 --- a/Lib/Src/Graphics/Renderer.cpp +++ b/Lib/Src/Graphics/Renderer.cpp @@ -57,7 +57,7 @@ namespace Tkge::Graphics } UpdateInstances(instances); - if (!WriteSets()) { return; } + if (!WriteSets(primitive.texture)) { return; } _renderPass->get_command_buffer().setViewport(0, _viewport); @@ -74,9 +74,10 @@ namespace Tkge::Graphics } } - bool Renderer::WriteSets() const + bool Renderer::WriteSets(const Texture* texture) const { auto& renderDevice = _renderPass->get_render_device(); + if (texture == nullptr) { texture = &_resourcePool->GetFallbackTexture(); } const auto setLayouts = _resourcePool->SetLayouts(); auto descriptorSets = std::array{}; @@ -84,6 +85,7 @@ namespace Tkge::Graphics if (!renderDevice.allocate_sets(descriptorSets, setLayouts)) { return false; } auto bufferInfos = klib::FlexArray{}; + auto imageInfos = klib::FlexArray{}; auto descriptorWrites = klib::FlexArray{}; const auto pushBufferWrite = [&](vk::DescriptorSet set, std::uint32_t binding, const Buffer& buffer, const vk::DescriptorType type) { @@ -95,6 +97,18 @@ namespace Tkge::Graphics descriptorWrites.push_back(wds); }; + const auto pushImageWrite = [&](vk::DescriptorSet set, std::uint32_t binding, const Texture& texture) + { + imageInfos.push_back({}); + auto& dii = imageInfos.back(); + dii.setImageView(texture.GetImage().get_view()) + .setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal) + .setSampler(_resourcePool->GetSampler(texture.sampler)); + auto wds = vk::WriteDescriptorSet{}; + wds.setImageInfo(dii).setDescriptorCount(1).setDescriptorType(vk::DescriptorType::eCombinedImageSampler).setDstSet(set).setDstBinding(binding); + descriptorWrites.push_back(wds); + }; + auto& ubo00 = _resourcePool->AllocateBuffer(vk::BufferUsageFlagBits::eUniformBuffer, sizeof(glm::mat4)); const auto halfRenderArea = 0.5f * glm::vec2{_viewport.width, -_viewport.height}; const auto matProj = glm::ortho(-halfRenderArea.x, halfRenderArea.x, -halfRenderArea.y, halfRenderArea.y); @@ -107,6 +121,8 @@ namespace Tkge::Graphics kvf::util::overwrite(ssbo01, instanceSpan); pushBufferWrite(descriptorSets[0], 1, ssbo01, vk::DescriptorType::eStorageBuffer); + pushImageWrite(descriptorSets[0], 2, *texture); + const auto writeSpan = std::span{descriptorWrites.data(), descriptorWrites.size()}; renderDevice.get_device().updateDescriptorSets(writeSpan, {}); diff --git a/Lib/Src/Graphics/Texture.cpp b/Lib/Src/Graphics/Texture.cpp new file mode 100644 index 0000000..919a502 --- /dev/null +++ b/Lib/Src/Graphics/Texture.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +namespace Tkge::Graphics +{ + namespace + { + constexpr auto ImageCreateInfo = kvf::vma::ImageCreateInfo{ + .format = vk::Format::eR8G8B8A8Srgb, + }; + + } + + Texture::Texture(gsl::not_null renderDevice) : _image(renderDevice, ImageCreateInfo) {} + + bool Texture::Create(const kvf::Bitmap& bitmap) + { + if (bitmap.bytes.empty()) { return false; } + return kvf::util::write_to(_image, bitmap); + } + + bool Texture::Decompress(std::span compressedImage) + { + if (compressedImage.empty()) { return false; } + const auto image = kvf::ImageBitmap{compressedImage}; + if (!image.is_loaded()) { return false; } + return Create(image.bitmap()); + } + + glm::ivec2 Texture::GetSize() const { return kvf::util::to_glm_vec(_image.get_extent()); } +} // namespace Tkge::Graphics