Skip to content

Commit bcff51d

Browse files
author
petr.kiyashko
committed
Wip PerFrameTransferHelper
1 parent 937db01 commit bcff51d

File tree

3 files changed

+246
-1
lines changed

3 files changed

+246
-1
lines changed

etna/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ add_library(etna
2222
"source/Window.cpp"
2323
"source/PerFrameCmdMgr.cpp"
2424
"source/OneShotCmdMgr.cpp"
25-
"source/BlockingTransferHelper.cpp")
25+
"source/BlockingTransferHelper.cpp"
26+
"source/PerFrameTransferHelper.cpp")
2627

2728
target_include_directories(etna PUBLIC include)
2829
target_include_directories(etna PRIVATE source)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
#ifndef ETNA_PER_FRAME_TRANSFER_HELPER_HPP_INCLUDED
3+
#define ETNA_PER_FRAME_TRANSFER_HELPER_HPP_INCLUDED
4+
5+
#include <etna/Vulkan.hpp>
6+
#include <etna/Buffer.hpp>
7+
#include <etna/Image.hpp>
8+
9+
#include <variant>
10+
11+
namespace etna
12+
{
13+
14+
/**
15+
* "non-blocking" GPU-CPU transfer helper:
16+
* Accepts upload/readback requests and serves them inside update call.
17+
* Supposed to be used for streaming-like operations in the main app loop.
18+
*/
19+
class PerFrameTransferHelper
20+
{
21+
public:
22+
struct CreateInfo
23+
{
24+
vk::DeviceSize stagingSize;
25+
};
26+
27+
explicit PerFrameTransferHelper(CreateInfo info);
28+
29+
PerFrameTransferHelper(const PerFrameTransferHelper&) = delete;
30+
PerFrameTransferHelper& operator=(const PerFrameTransferHelper&) = delete;
31+
PerFrameTransferHelper(PerFrameTransferHelper&&) = delete;
32+
PerFrameTransferHelper& operator=(PerFrameTransferHelper&&) = delete;
33+
34+
void update(vk::CommandBuffer cmd_buf);
35+
36+
void requestUploadImage(
37+
Image&& dst,
38+
std::span<std::byte const> src,
39+
uint32_t mip_level,
40+
uint32_t layer,
41+
std::function<void(Image&&)> &&return_dst_cb);
42+
43+
private:
44+
struct ImageUploadRequest
45+
{
46+
Image dst;
47+
std::span<std::byte const> src;
48+
uint32_t mipLevel;
49+
uint32_t layer;
50+
size_t bytesPerPixel;
51+
size_t bytesPerLine;
52+
size_t uploadedLines;
53+
std::function<void(Image&&)> returnDstCb;
54+
};
55+
struct BufferUploadRequest
56+
{
57+
};
58+
using Request = std::variant<ImageUploadRequest, BufferUploadRequest>;
59+
60+
struct UpdateContext
61+
{
62+
vk::CommandBuffer cmdBuf;
63+
vk::DeviceSize stagingUsed = 0;
64+
std::vector<size_t> fulfilledRequestIds{};
65+
};
66+
67+
vk::DeviceSize stagingSize;
68+
Buffer stagingBuffer;
69+
std::vector<Request> requestQueue{};
70+
71+
bool serveImageUploadRequest(UpdateContext &ctx, ImageUploadRequest &r);
72+
bool serveBufferUploadRequest(UpdateContext &ctx, BufferUploadRequest &r);
73+
};
74+
75+
} // namespace etna
76+
77+
#endif
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#include <etna/PerFrameTransferHelper.hpp>
2+
#include <etna/Etna.hpp>
3+
4+
#include <vulkan/vulkan_format_traits.hpp>
5+
6+
namespace etna
7+
{
8+
9+
PerFrameTransferHelper::PerFrameTransferHelper(CreateInfo info)
10+
: stagingSize{info.stagingSize}
11+
, stagingBuffer{etna::get_context().createBuffer(Buffer::CreateInfo{
12+
.size = stagingSize,
13+
.bufferUsage = vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eTransferSrc,
14+
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
15+
.allocationCreate =
16+
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
17+
.name = "PerFrameTransferHelper::stagingBuffer",
18+
})}
19+
{
20+
stagingBuffer.map();
21+
}
22+
23+
void PerFrameTransferHelper::update(vk::CommandBuffer cmd_buf)
24+
{
25+
UpdateContext ctx{.cmdBuf = cmd_buf};
26+
for (size_t requestId = 0; auto &request : requestQueue)
27+
{
28+
std::visit([&, this](auto &r) {
29+
bool fulfilled;
30+
if constexpr (std::is_same_v<std::remove_cvref_t<decltype(r)>, ImageUploadRequest>)
31+
fulfilled = serveImageUploadRequest(ctx, r);
32+
else
33+
fulfilled = serveBufferUploadRequest(ctx, r);
34+
if (fulfilled)
35+
ctx.fulfilledRequestIds.push_back(requestId);
36+
}, request);
37+
++requestId;
38+
}
39+
for (auto it = ctx.fulfilledRequestIds.rbegin(); it != ctx.fulfilledRequestIds.rend(); ++it)
40+
requestQueue.erase(requestQueue.begin() + (*it));
41+
}
42+
43+
void PerFrameTransferHelper::requestUploadImage(
44+
Image&& dst,
45+
std::span<std::byte const> src,
46+
uint32_t mip_level,
47+
uint32_t layer,
48+
std::function<void(Image&&)> &&return_dst_cb)
49+
{
50+
// @TODO: this duplicated code with BlockingTransferHelper!
51+
auto [w, h, d] = dst.getExtent();
52+
53+
size_t const bytesPerPixel = vk::blockSize(dst.getFormat());
54+
55+
ETNA_ASSERTF(d == 1, "3D image uploads are not implemented yet!");
56+
57+
ETNA_ASSERTF(
58+
w * h * bytesPerPixel == src.size(),
59+
"Image size mismatch between CPU and GPU! Expected {} bytes, but got {}!",
60+
w * h * bytesPerPixel,
61+
src.size());
62+
63+
size_t const bytesPerLine = w * bytesPerPixel;
64+
size_t const maxLinesPerUpload = stagingSize / bytesPerLine;
65+
ETNA_ASSERTF(
66+
maxLinesPerUpload > 0,
67+
"Unable to fit a single line into the staging buffer! Buffer size is {} bytes, but a single "
68+
"line is {} bytes!",
69+
stagingSize,
70+
w * bytesPerPixel);
71+
72+
requestQueue.emplace_back(
73+
ImageUploadRequest{
74+
std::move(dst),
75+
src,
76+
mip_level,
77+
layer,
78+
bytesPerPixel,
79+
bytesPerLine,
80+
0,
81+
std::move(return_dst_cb)});
82+
}
83+
84+
bool PerFrameTransferHelper::serveImageUploadRequest(UpdateContext &ctx, ImageUploadRequest &r)
85+
{
86+
auto [w, h, _] = r.dst.getExtent();
87+
88+
// @TODO: pull out
89+
auto const alignUp = [](size_t id, size_t to) { return ((id - 1) / to + 1) * to; };
90+
vk::DeviceSize offset = alignUp(ctx.stagingUsed, r.bytesPerPixel);
91+
vk::DeviceSize space = stagingSize - offset;
92+
93+
if (space < r.bytesPerLine)
94+
return false;
95+
96+
size_t const linesLeft = h - r.uploadedLines;
97+
size_t const linesThisUpload = std::min(space / r.bytesPerLine, linesLeft);
98+
size_t const bytesThisUpload = linesThisUpload * r.bytesPerLine;
99+
100+
memcpy(
101+
stagingBuffer.data() + ctx.stagingUsed,
102+
r.src.data() + r.uploadedLines * r.bytesPerLine,
103+
bytesThisUpload);
104+
105+
if (r.uploadedLines == 0)
106+
{
107+
etna::set_state(
108+
ctx.cmdBuf,
109+
r.dst.get(),
110+
vk::PipelineStageFlagBits2::eTransfer,
111+
vk::AccessFlagBits2::eTransferWrite,
112+
vk::ImageLayout::eTransferDstOptimal,
113+
r.dst.getAspectMaskByFormat());
114+
etna::flush_barriers(ctx.cmdBuf);
115+
}
116+
117+
vk::BufferImageCopy2 copy{
118+
.bufferOffset = offset,
119+
.bufferRowLength = 0,
120+
.bufferImageHeight = 0,
121+
.imageSubresource =
122+
vk::ImageSubresourceLayers{
123+
.aspectMask = r.dst.getAspectMaskByFormat(),
124+
.mipLevel = r.mipLevel,
125+
.baseArrayLayer = r.layer,
126+
.layerCount = 1,
127+
},
128+
.imageOffset = vk::Offset3D{0, static_cast<int32_t>(r.uploadedLines), 0},
129+
.imageExtent = vk::Extent3D{w, static_cast<uint32_t>(linesThisUpload), 1},
130+
};
131+
vk::CopyBufferToImageInfo2 info{
132+
.srcBuffer = stagingBuffer.get(),
133+
.dstImage = r.dst.get(),
134+
.dstImageLayout = vk::ImageLayout::eTransferDstOptimal,
135+
.regionCount = 1,
136+
.pRegions = &copy,
137+
};
138+
ctx.cmdBuf.copyBufferToImage2(info);
139+
140+
ctx.stagingUsed = offset + bytesThisUpload;
141+
r.uploadedLines += linesThisUpload;
142+
143+
if (r.uploadedLines < h)
144+
return false;
145+
146+
// Done
147+
etna::set_state(
148+
ctx.cmdBuf,
149+
r.dst.get(),
150+
{},
151+
{},
152+
vk::ImageLayout::eShaderReadOnlyOptimal,
153+
r.dst.getAspectMaskByFormat());
154+
etna::flush_barriers(ctx.cmdBuf);
155+
156+
r.returnDstCb(std::move(r.dst));
157+
return true;
158+
}
159+
160+
bool PerFrameTransferHelper::serveBufferUploadRequest(UpdateContext &ctx, BufferUploadRequest &r)
161+
{
162+
(void)ctx, (void)r;
163+
ETNA_ASSERTF(0, "Not implemented (PerFrameTransferHelper::serveBufferUploadRequest)");
164+
return false;
165+
}
166+
167+
} // namespace etna

0 commit comments

Comments
 (0)