Skip to content

Commit 2f3cafe

Browse files
committed
Texture
1 parent 82d5dbd commit 2f3cafe

File tree

12 files changed

+391
-7
lines changed

12 files changed

+391
-7
lines changed

assets/shader.frag

280 Bytes
Binary file not shown.

assets/shader.vert

148 Bytes
Binary file not shown.

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
- [Descriptor Sets](descriptor_sets/README.md)
4747
- [Pipeline Layout](descriptor_sets/pipeline_layout.md)
4848
- [Shader Buffer](descriptor_sets/shader_buffer.md)
49+
- [Texture](descriptor_sets/texture.md)
22.1 KB
Loading

guide/src/descriptor_sets/texture.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Texture
2+
3+
With a large part of the complexity wrapped away in `vma`, a `Texture` is just a combination of three things:
4+
5+
1. Sampled Image
6+
2. (Unique) Image View of above
7+
3. (Unique) Sampler
8+
9+
In `texture.hpp`, create a default sampler:
10+
11+
```cpp
12+
[[nodiscard]] constexpr auto create_sampler_ci(vk::SamplerAddressMode wrap,
13+
vk::Filter filter) {
14+
auto ret = vk::SamplerCreateInfo{};
15+
ret.setAddressModeU(wrap)
16+
.setAddressModeV(wrap)
17+
.setAddressModeW(wrap)
18+
.setMinFilter(filter)
19+
.setMagFilter(filter)
20+
.setMaxLod(VK_LOD_CLAMP_NONE)
21+
.setBorderColor(vk::BorderColor::eFloatTransparentBlack)
22+
.setMipmapMode(vk::SamplerMipmapMode::eNearest);
23+
return ret;
24+
}
25+
26+
constexpr auto sampler_ci_v = create_sampler_ci(
27+
vk::SamplerAddressMode::eClampToEdge, vk::Filter::eLinear);
28+
```
29+
30+
Define the Create Info and Texture types:
31+
32+
```cpp
33+
struct TextureCreateInfo {
34+
vk::Device device;
35+
VmaAllocator allocator;
36+
std::uint32_t queue_family;
37+
CommandBlock command_block;
38+
Bitmap bitmap;
39+
40+
vk::SamplerCreateInfo sampler{sampler_ci_v};
41+
};
42+
43+
class Texture {
44+
public:
45+
using CreateInfo = TextureCreateInfo;
46+
47+
explicit Texture(CreateInfo create_info);
48+
49+
[[nodiscard]] auto descriptor_info() const -> vk::DescriptorImageInfo;
50+
51+
private:
52+
vma::Image m_image{};
53+
vk::UniqueImageView m_view{};
54+
vk::UniqueSampler m_sampler{};
55+
};
56+
```
57+
58+
Add a fallback bitmap constant, and the implementation:
59+
60+
```cpp
61+
// 4-channels.
62+
constexpr auto white_pixel_v = std::array{std::byte{0xff}, std::byte{0xff},
63+
std::byte{0xff}, std::byte{0xff}};
64+
// fallback bitmap.
65+
constexpr auto white_bitmap_v = Bitmap{
66+
.bytes = white_pixel_v,
67+
.size = {1, 1},
68+
};
69+
70+
// ...
71+
Texture::Texture(CreateInfo create_info) {
72+
if (create_info.bitmap.bytes.empty() || create_info.bitmap.size.x <= 0 ||
73+
create_info.bitmap.size.y <= 0) {
74+
create_info.bitmap = white_bitmap_v;
75+
}
76+
77+
auto const image_ci = vma::ImageCreateInfo{
78+
.allocator = create_info.allocator,
79+
.queue_family = create_info.queue_family,
80+
};
81+
m_image = vma::create_sampled_image(
82+
image_ci, std::move(create_info.command_block), create_info.bitmap);
83+
84+
auto image_view_ci = vk::ImageViewCreateInfo{};
85+
auto subresource_range = vk::ImageSubresourceRange{};
86+
subresource_range.setAspectMask(vk::ImageAspectFlagBits::eColor)
87+
.setLayerCount(1)
88+
.setLevelCount(m_image.get().levels);
89+
90+
image_view_ci.setImage(m_image.get().image)
91+
.setViewType(vk::ImageViewType::e2D)
92+
.setFormat(m_image.get().format)
93+
.setSubresourceRange(subresource_range);
94+
m_view = create_info.device.createImageViewUnique(image_view_ci);
95+
96+
m_sampler = create_info.device.createSamplerUnique(create_info.sampler);
97+
}
98+
99+
auto Texture::descriptor_info() const -> vk::DescriptorImageInfo {
100+
auto ret = vk::DescriptorImageInfo{};
101+
ret.setImageView(*m_view)
102+
.setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
103+
.setSampler(*m_sampler);
104+
return ret;
105+
}
106+
```
107+
108+
To sample textures, `Vertex` will need a UV coordinate:
109+
110+
```cpp
111+
struct Vertex {
112+
glm::vec2 position{};
113+
glm::vec3 color{1.0f};
114+
glm::vec2 uv{};
115+
};
116+
117+
// two vertex attributes: position at 0, color at 1.
118+
constexpr auto vertex_attributes_v = std::array{
119+
// the format matches the type and layout of data: vec2 => 2x 32-bit floats.
120+
vk::VertexInputAttributeDescription2EXT{0, 0, vk::Format::eR32G32Sfloat,
121+
offsetof(Vertex, position)},
122+
// vec3 => 3x 32-bit floats
123+
vk::VertexInputAttributeDescription2EXT{1, 0, vk::Format::eR32G32B32Sfloat,
124+
offsetof(Vertex, color)},
125+
// vec2 => 2x 32-bit floats
126+
vk::VertexInputAttributeDescription2EXT{2, 0, vk::Format::eR32G32Sfloat,
127+
offsetof(Vertex, uv)},
128+
};
129+
```
130+
131+
Store a texture in `App` and create with the other shader resources:
132+
133+
```cpp
134+
std::optional<Texture> m_texture{};
135+
136+
// ...
137+
using Pixel = std::array<std::byte, 4>;
138+
static constexpr auto rgby_pixels_v = std::array{
139+
Pixel{std::byte{0xff}, {}, {}, std::byte{0xff}},
140+
Pixel{std::byte{}, std::byte{0xff}, {}, std::byte{0xff}},
141+
Pixel{std::byte{}, {}, std::byte{0xff}, std::byte{0xff}},
142+
Pixel{std::byte{0xff}, std::byte{0xff}, {}, std::byte{0xff}},
143+
};
144+
static constexpr auto rgby_bytes_v =
145+
std::bit_cast<std::array<std::byte, sizeof(rgby_pixels_v)>>(
146+
rgby_pixels_v);
147+
static constexpr auto rgby_bitmap_v = Bitmap{
148+
.bytes = rgby_bytes_v,
149+
.size = {2, 2},
150+
};
151+
auto texture_ci = Texture::CreateInfo{
152+
.device = *m_device,
153+
.allocator = m_allocator.get(),
154+
.queue_family = m_gpu.queue_family,
155+
.command_block = create_command_block(),
156+
.bitmap = rgby_bitmap_v,
157+
};
158+
// use Nearest filtering instead of Linear (interpolation).
159+
texture_ci.sampler.setMagFilter(vk::Filter::eNearest);
160+
m_texture.emplace(std::move(texture_ci));
161+
```
162+
163+
Update the descriptor pool sizes to also contain Combined Image Samplers:
164+
165+
```cpp
166+
/// ...
167+
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 2},
168+
vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler, 2},
169+
```
170+
171+
Set up a new descriptor set (number 1) with a combined image sampler at binding 0. This could be added to binding 1 of set 0 as well, since we are not optimizing binding calls (eg binding set 0 only once for multiple draws):
172+
173+
```cpp
174+
static constexpr auto set_1_bindings_v = std::array{
175+
layout_binding(0, vk::DescriptorType::eCombinedImageSampler),
176+
};
177+
auto set_layout_cis = std::array<vk::DescriptorSetLayoutCreateInfo, 2>{};
178+
set_layout_cis[0].setBindings(set_0_bindings_v);
179+
set_layout_cis[1].setBindings(set_1_bindings_v);
180+
```
181+
182+
Remove the vertex colors and set the UVs for the quad. In Vulkan UV space is the same as GLFW window space: origin is at the top left, +X moves right, +Y moves down.
183+
184+
```cpp
185+
static constexpr auto vertices_v = std::array{
186+
Vertex{.position = {-200.0f, -200.0f}, .uv = {0.0f, 1.0f}},
187+
Vertex{.position = {200.0f, -200.0f}, .uv = {1.0f, 1.0f}},
188+
Vertex{.position = {200.0f, 200.0f}, .uv = {1.0f, 0.0f}},
189+
Vertex{.position = {-200.0f, 200.0f}, .uv = {0.0f, 0.0f}},
190+
};
191+
```
192+
193+
Finally, update the descriptor writes:
194+
195+
```cpp
196+
auto writes = std::array<vk::WriteDescriptorSet, 2>{};
197+
// ...
198+
auto const set1 = descriptor_sets[1];
199+
auto const image_info = m_texture->descriptor_info();
200+
write.setImageInfo(image_info)
201+
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
202+
.setDescriptorCount(1)
203+
.setDstSet(set1)
204+
.setDstBinding(0);
205+
writes[1] = write;
206+
```
207+
208+
Since set 1 is not N-buffered (because the Texture is "GPU const"), in this case the sets could also be updated once after texture creation instead of every frame.
209+
210+
Add the UV vertex attribute the vertex shader and pass it to the fragment shader:
211+
212+
```glsl
213+
layout (location = 2) in vec2 a_uv;
214+
215+
// ...
216+
layout (location = 1) out vec2 out_uv;
217+
218+
// ...
219+
out_color = a_color;
220+
out_uv = a_uv;
221+
```
222+
223+
Add set 1 and the incoming UV coords to the fragment shader, combine the sampled texture color with the vertex color:
224+
225+
```glsl
226+
layout (set = 1, binding = 0) uniform sampler2D tex;
227+
228+
// ...
229+
layout (location = 1) in vec2 in_uv;
230+
231+
// ...
232+
out_color = vec4(in_color, 1.0) * texture(tex, in_uv);
233+
```
234+
235+
![RGBY Texture](./rgby_texture.png)

src/app.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ void App::create_descriptor_pool() {
257257
static constexpr auto pool_sizes_v = std::array{
258258
// 2 uniform buffers, can be more if desired.
259259
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 2},
260+
vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler, 2},
260261
};
261262
auto pool_ci = vk::DescriptorPoolCreateInfo{};
262263
// allow 16 sets to be allocated from this pool.
@@ -268,8 +269,12 @@ void App::create_pipeline_layout() {
268269
static constexpr auto set_0_bindings_v = std::array{
269270
layout_binding(0, vk::DescriptorType::eUniformBuffer),
270271
};
271-
auto set_layout_cis = std::array<vk::DescriptorSetLayoutCreateInfo, 1>{};
272+
static constexpr auto set_1_bindings_v = std::array{
273+
layout_binding(0, vk::DescriptorType::eCombinedImageSampler),
274+
};
275+
auto set_layout_cis = std::array<vk::DescriptorSetLayoutCreateInfo, 2>{};
272276
set_layout_cis[0].setBindings(set_0_bindings_v);
277+
set_layout_cis[1].setBindings(set_1_bindings_v);
273278

274279
for (auto const& set_layout_ci : set_layout_cis) {
275280
m_set_layouts.push_back(
@@ -314,10 +319,10 @@ void App::create_cmd_block_pool() {
314319
void App::create_shader_resources() {
315320
// vertices of a quad.
316321
static constexpr auto vertices_v = std::array{
317-
Vertex{.position = {-200.0f, -200.0f}, .color = {1.0f, 0.0f, 0.0f}},
318-
Vertex{.position = {200.0f, -200.0f}, .color = {0.0f, 1.0f, 0.0f}},
319-
Vertex{.position = {200.0f, 200.0f}, .color = {0.0f, 0.0f, 1.0f}},
320-
Vertex{.position = {-200.0f, 200.0f}, .color = {1.0f, 1.0f, 0.0f}},
322+
Vertex{.position = {-200.0f, -200.0f}, .uv = {0.0f, 1.0f}},
323+
Vertex{.position = {200.0f, -200.0f}, .uv = {1.0f, 1.0f}},
324+
Vertex{.position = {200.0f, 200.0f}, .uv = {1.0f, 0.0f}},
325+
Vertex{.position = {-200.0f, 200.0f}, .uv = {0.0f, 0.0f}},
321326
};
322327
static constexpr auto indices_v = std::array{
323328
0u, 1u, 2u, 2u, 3u, 0u,
@@ -341,6 +346,31 @@ void App::create_shader_resources() {
341346

342347
m_view_ubo.emplace(m_allocator.get(), m_gpu.queue_family,
343348
vk::BufferUsageFlagBits::eUniformBuffer);
349+
350+
using Pixel = std::array<std::byte, 4>;
351+
static constexpr auto rgby_pixels_v = std::array{
352+
Pixel{std::byte{0xff}, {}, {}, std::byte{0xff}},
353+
Pixel{std::byte{}, std::byte{0xff}, {}, std::byte{0xff}},
354+
Pixel{std::byte{}, {}, std::byte{0xff}, std::byte{0xff}},
355+
Pixel{std::byte{0xff}, std::byte{0xff}, {}, std::byte{0xff}},
356+
};
357+
static constexpr auto rgby_bytes_v =
358+
std::bit_cast<std::array<std::byte, sizeof(rgby_pixels_v)>>(
359+
rgby_pixels_v);
360+
static constexpr auto rgby_bitmap_v = Bitmap{
361+
.bytes = rgby_bytes_v,
362+
.size = {2, 2},
363+
};
364+
auto texture_ci = Texture::CreateInfo{
365+
.device = *m_device,
366+
.allocator = m_allocator.get(),
367+
.queue_family = m_gpu.queue_family,
368+
.command_block = create_command_block(),
369+
.bitmap = rgby_bitmap_v,
370+
};
371+
// use Nearest filtering instead of Linear (interpolation).
372+
texture_ci.sampler.setMagFilter(vk::Filter::eNearest);
373+
m_texture.emplace(std::move(texture_ci));
344374
}
345375

346376
void App::create_descriptor_sets() {
@@ -560,7 +590,7 @@ void App::draw(vk::CommandBuffer const command_buffer) const {
560590
}
561591

562592
void App::bind_descriptor_sets(vk::CommandBuffer const command_buffer) const {
563-
auto writes = std::array<vk::WriteDescriptorSet, 1>{};
593+
auto writes = std::array<vk::WriteDescriptorSet, 2>{};
564594
auto const& descriptor_sets = m_descriptor_sets.at(m_frame_index);
565595
auto const set0 = descriptor_sets[0];
566596
auto write = vk::WriteDescriptorSet{};
@@ -571,6 +601,16 @@ void App::bind_descriptor_sets(vk::CommandBuffer const command_buffer) const {
571601
.setDstSet(set0)
572602
.setDstBinding(0);
573603
writes[0] = write;
604+
605+
auto const set1 = descriptor_sets[1];
606+
auto const image_info = m_texture->descriptor_info();
607+
write.setImageInfo(image_info)
608+
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
609+
.setDescriptorCount(1)
610+
.setDstSet(set1)
611+
.setDstBinding(0);
612+
writes[1] = write;
613+
574614
m_device->updateDescriptorSets(writes, {});
575615

576616
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,

src/app.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <shader_buffer.hpp>
88
#include <shader_program.hpp>
99
#include <swapchain.hpp>
10+
#include <texture.hpp>
1011
#include <vma.hpp>
1112
#include <window.hpp>
1213
#include <filesystem>
@@ -99,6 +100,7 @@ class App {
99100

100101
vma::Buffer m_vbo{};
101102
std::optional<ShaderBuffer> m_view_ubo{};
103+
std::optional<Texture> m_texture{};
102104
Buffered<std::vector<vk::DescriptorSet>> m_descriptor_sets{};
103105

104106
glm::ivec2 m_framebuffer_size{};

src/glsl/shader.frag

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#version 450 core
22

3+
layout (set = 1, binding = 0) uniform sampler2D tex;
4+
35
layout (location = 0) in vec3 in_color;
6+
layout (location = 1) in vec2 in_uv;
47

58
layout (location = 0) out vec4 out_color;
69

710
void main() {
8-
out_color = vec4(in_color, 1.0);
11+
out_color = vec4(in_color, 1.0) * texture(tex, in_uv);
912
}

src/glsl/shader.vert

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22

33
layout (location = 0) in vec2 a_pos;
44
layout (location = 1) in vec3 a_color;
5+
layout (location = 2) in vec2 a_uv;
56

67
layout (set = 0, binding = 0) uniform View {
78
mat4 mat_vp;
89
};
910

1011
layout (location = 0) out vec3 out_color;
12+
layout (location = 1) out vec2 out_uv;
1113

1214
void main() {
1315
const vec4 world_pos = vec4(a_pos, 0.0, 1.0);
1416

1517
out_color = a_color;
18+
out_uv = a_uv;
1619
gl_Position = mat_vp * world_pos;
1720
}

0 commit comments

Comments
 (0)