Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Add Graphics::Texture, demo 2x2 RGBM texture #26

Merged
merged 8 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion App/Src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <Tkge/Graphics/Drawable.hpp>
#include <Tkge/Graphics/Shader.hpp>
#include <klib/assert.hpp>
#include <kvf/color_bitmap.hpp>
#include <kvf/time.hpp>
#include <cmath>
#include <exception>
Expand Down Expand Up @@ -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});
Expand Down
Binary file modified Assets/Shaders/Default.frag
Binary file not shown.
Binary file modified Assets/Shaders/Default.vert
Binary file not shown.
1 change: 1 addition & 0 deletions Lib/Include/Tkge/Engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
3 changes: 3 additions & 0 deletions Lib/Include/Tkge/Graphics/Drawable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ namespace Tkge::Graphics
.vertices = this->GetVertices(),
.indices = this->GetIndices(),
.topology = this->GetTopology(),
.texture = texture,
};
}

const Texture* texture{nullptr};
};

template <std::derived_from<IGeometry> TGeometry>
Expand Down
2 changes: 2 additions & 0 deletions Lib/Include/Tkge/Graphics/Primitive.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include <Tkge/Graphics/Texture.hpp>
#include <Tkge/Graphics/Vertex.hpp>
#include <span>

Expand All @@ -10,5 +11,6 @@ namespace Tkge::Graphics
std::span<const std::uint32_t> indices{};

vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList};
const Texture* texture{nullptr};
};
} // namespace Tkge::Graphics
2 changes: 1 addition & 1 deletion Lib/Include/Tkge/Graphics/Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ namespace Tkge::Graphics
};

void UpdateInstances(std::span<const RenderInstance> 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{};
Expand Down
4 changes: 4 additions & 0 deletions Lib/Include/Tkge/Graphics/ResourcePool.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
#include <Tkge/Graphics/PipelineFixedState.hpp>
#include <Tkge/Graphics/Shader.hpp>
#include <Tkge/Graphics/Texture.hpp>
#include <kvf/vma.hpp>

namespace Tkge::Graphics
Expand All @@ -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
29 changes: 29 additions & 0 deletions Lib/Include/Tkge/Graphics/Texture.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once
#include <Tkge/Graphics/TextureSampler.hpp>
#include <kvf/bitmap.hpp>
#include <kvf/render_device.hpp>
#include <kvf/vma.hpp>
#include <cstddef>
#include <span>

namespace Tkge::Graphics
{
class Texture
{
public:
using Sampler = TextureSampler;

explicit Texture(gsl::not_null<kvf::RenderDevice*> renderDevice);

bool Create(const kvf::Bitmap& bitmap);
bool Decompress(std::span<const std::byte> 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
15 changes: 15 additions & 0 deletions Lib/Include/Tkge/Graphics/TextureSampler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#include <vulkan/vulkan.hpp>

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
5 changes: 3 additions & 2 deletions Lib/Src/Detail/PipelinePool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ namespace Tkge::Detail
{
static constexpr auto StageFlags = vk::ShaderStageFlagBits::eAllGraphics;
// set 0: builtin
auto set0 = std::array<vk::DescriptorSetLayoutBinding, 2>{};
auto set0 = std::array<vk::DescriptorSetLayoutBinding, 3>{};
// 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

Expand Down
37 changes: 37 additions & 0 deletions Lib/Src/Detail/SamplerPool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once
#include <Tkge/Graphics/TextureSampler.hpp>
#include <klib/hash_combine.hpp>
#include <kvf/render_device.hpp>
#include <vulkan/vulkan_hash.hpp>
#include <unordered_map>

namespace Tkge::Detail
{
class SamplerPool
{
public:
explicit SamplerPool(gsl::not_null<const kvf::RenderDevice*> 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<const kvf::RenderDevice*> _renderDevice;

std::unordered_map<std::size_t, vk::UniqueSampler> _samplers{};
};
} // namespace Tkge::Detail
18 changes: 17 additions & 1 deletion Lib/Src/Engine.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <Detail/BufferPool.hpp>
#include <Detail/PipelinePool.hpp>
#include <Detail/SamplerPool.hpp>
#include <Tkge/Engine.hpp>
#include <klib/assert.hpp>
#include <kvf/is_positive.hpp>
Expand All @@ -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<std::byte, 4> bytes{};
};

constexpr auto WhiteBitmap = std::bit_cast<PixelBitmap>(kvf::white_v);

class ResourcePool : public Graphics::IResourcePool
{
public:
explicit ResourcePool(gsl::not_null<kvf::RenderDevice*> 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(); }
Expand All @@ -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

Expand Down
5 changes: 4 additions & 1 deletion Lib/Src/Glsl/Default.frag
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions Lib/Src/Glsl/Default.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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;
}
20 changes: 18 additions & 2 deletions Lib/Src/Graphics/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace Tkge::Graphics
}

UpdateInstances(instances);
if (!WriteSets()) { return; }
if (!WriteSets(primitive.texture)) { return; }

_renderPass->get_command_buffer().setViewport(0, _viewport);

Expand All @@ -74,16 +74,18 @@ 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<vk::DescriptorSet, 1>{};
KLIB_ASSERT(setLayouts.size() == descriptorSets.size()); // expected set count will change until render flow is stable
if (!renderDevice.allocate_sets(descriptorSets, setLayouts)) { return false; }

auto bufferInfos = klib::FlexArray<vk::DescriptorBufferInfo, 4>{};
auto imageInfos = klib::FlexArray<vk::DescriptorImageInfo, 2>{};
auto descriptorWrites = klib::FlexArray<vk::WriteDescriptorSet, 8>{};
const auto pushBufferWrite = [&](vk::DescriptorSet set, std::uint32_t binding, const Buffer& buffer, const vk::DescriptorType type)
{
Expand All @@ -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);
Expand All @@ -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, {});

Expand Down
32 changes: 32 additions & 0 deletions Lib/Src/Graphics/Texture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <Tkge/Graphics/Texture.hpp>
#include <kvf/image_bitmap.hpp>
#include <kvf/util.hpp>

namespace Tkge::Graphics
{
namespace
{
constexpr auto ImageCreateInfo = kvf::vma::ImageCreateInfo{
.format = vk::Format::eR8G8B8A8Srgb,
};

}

Texture::Texture(gsl::not_null<kvf::RenderDevice*> 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<const std::byte> 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<int>(_image.get_extent()); }
} // namespace Tkge::Graphics