Textured Quad
This example demonstrates using KDGpu::Device::createTexture on a static image loaded from disk using the STB image header.
First, we include the STB image header, which will provide us with the necessary inputs to the KDGpu API:
| #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
|
Filename: textured_quad/textured_quad.cpp
We organize all of the STB-image-supplied values into one struct, using an RGBA KDGpu::Format.
| struct ImageData {
uint32_t width{ 0 };
uint32_t height{ 0 };
uint8_t *pixelData{ nullptr };
DeviceSize byteSize{ 0 };
Format format{ Format::R8G8B8A8_UNORM };
};
|
Filename: textured_quad/textured_quad.cpp
And we have a function to populate the struct, with some per-platform implementation details. This function includes all of the STB image calls in this example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | ImageData loadImage(const std::string &path)
{
int texChannels;
int _width = 0, _height = 0;
std::string texturePath = path;
#ifdef PLATFORM_WIN32
// STB fails to load if path is /C:/... instead of C:/...
if (texturePath.rfind("/", 0) == 0)
texturePath = texturePath.substr(1);
#endif
auto _data = stbi_load(texturePath.c_str(), &_width, &_height, &texChannels, STBI_rgb_alpha);
if (_data == nullptr) {
SPDLOG_WARN("Failed to load texture {} {}", path, stbi_failure_reason());
return {};
}
SPDLOG_DEBUG("Texture dimensions: {} x {}", _width, _height);
return ImageData{
.width = static_cast<uint32_t>(_width),
.height = static_cast<uint32_t>(_height),
.pixelData = static_cast<uint8_t *>(_data),
.byteSize = 4 * static_cast<DeviceSize>(_width) * static_cast<DeviceSize>(_height)
};
}
|
Filename: textured_quad/textured_quad.cpp
Initialization
At scene initialization, we load and upload the texture, and create and upload the vertex buffer for the quad. The quad vertex buffer creation is unsurprising, but texture upload is a set of new function calls. Some settings to note:
- We have no need to access the buffer after loading, so the memory usage is GPU-only.
- We perform the texture upload with only one copy region which covers the whole texture. This is just boilerplate for us but a larger texture could make use of multiple copy regions.
- The
oldLayout
and newLayout
options offer a way to optimize the texture for different usecases. Check out KDGpu::TextureLayout to see the available layouts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 | {
// Load the image data and size
ImageData image = loadImage(KDGpu::assetPath() + "/textures/samuel-ferrara-1527pjeb6jg-unsplash.jpg");
const TextureOptions textureOptions = {
.type = TextureType::TextureType2D,
.format = image.format,
.extent = { .width = image.width, .height = image.height, .depth = 1 },
.mipLevels = 1,
.usage = TextureUsageFlagBits::SampledBit | TextureUsageFlagBits::TransferDstBit,
.memoryUsage = MemoryUsage::GpuOnly,
.initialLayout = TextureLayout::Undefined
};
m_texture = m_device.createTexture(textureOptions);
// Upload the texture data and transition to ShaderReadOnlyOptimal
// clang-format off
const std::vector<BufferTextureCopyRegion> regions = {{
.textureSubResource = { .aspectMask = TextureAspectFlagBits::ColorBit },
.textureExtent = { .width = image.width, .height = image.height, .depth = 1 }
}};
// clang-format on
const TextureUploadOptions uploadOptions = {
.destinationTexture = m_texture,
.dstStages = PipelineStageFlagBit::AllGraphicsBit,
.dstMask = AccessFlagBit::MemoryReadBit,
.data = image.pixelData,
.byteSize = image.byteSize,
.oldLayout = TextureLayout::Undefined,
.newLayout = TextureLayout::ShaderReadOnlyOptimal,
.regions = regions
};
uploadTextureData(uploadOptions);
// Create a view and sampler
m_textureView = m_texture.createView();
m_sampler = m_device.createSampler();
}
|
Filename: textured_quad/textured_quad.cpp
When initializing the graphics pipeline, we pass in a KDGpu::PrimitiveOptions to the primitive
field, which in turn contains KDGpu::PrimitiveTopology field. The topology declares how the vertices will be interpreted as polygons. Another way to achieve this is with an index buffer. In our case, a quad is well described by an existing topology option, so we use that.
| .primitive = {
.topology = PrimitiveTopology::TriangleStrip
}
|
Filename: textured_quad/textured_quad.cpp
We also create a bindgroup with a TextureViewBinding
resource for sampling this static texture:
| const BindGroupOptions bindGroupOptions = {
.layout = bindGroupLayout,
.resources = {{
.binding = 0,
.resource = TextureViewSamplerBinding{ .textureView = m_textureView, .sampler = m_sampler }
}}
};
// clang-format on
m_textureBindGroup = m_device.createBindGroup(bindGroupOptions);
|
Filename: textured_quad/textured_quad.cpp
Per-Frame Rendering Logic
Now that we have initialized everything properly, the render function is one of the simplest so far. We set the pipeline and buffer as usual, and set the bindgroup we just created.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | void TexturedQuad::render()
{
auto commandRecorder = m_device.createCommandRecorder();
m_opaquePassOptions.colorAttachments[0].view = m_swapchainViews.at(m_currentSwapchainImageIndex);
auto opaquePass = commandRecorder.beginRenderPass(m_opaquePassOptions);
opaquePass.setPipeline(m_pipeline);
opaquePass.setVertexBuffer(0, m_buffer);
opaquePass.setBindGroup(0, m_textureBindGroup);
opaquePass.draw(DrawCommand{ .vertexCount = 4 });
renderImGuiOverlay(&opaquePass);
opaquePass.end();
m_commandBuffer = commandRecorder.finish();
const SubmitOptions submitOptions = {
.commandBuffers = { m_commandBuffer },
.waitSemaphores = { m_presentCompleteSemaphores[m_inFlightIndex] },
.signalSemaphores = { m_renderCompleteSemaphores[m_inFlightIndex] }
};
m_queue.submit(submitOptions);
}
|
Filename: textured_quad/textured_quad.cpp
Also, be sure to actually sample from the texture in the shader:
| layout(location = 0) in vec2 texCoord;
layout(location = 0) out vec4 fragColor;
layout(set = 0, binding = 0) uniform sampler2D colorTexture;
void main()
{
vec3 color = texture(colorTexture, texCoord).rgb;
fragColor = vec4(color, 1.0);
}
|
Filename: textured_quad/doc/shadersnippet.frag
Updated on 2024-08-28 at 00:05:10 +0000