Loading...
Searching...
No Matches
BorrowedHostImportCapture.hpp
Go to the documentation of this file.
1#pragma once
2
42
43#include <QtGui/private/qrhi_p.h>
44
45#include <QDebug>
46
47#include <cstddef>
48#include <cstdint>
49#include <string>
50#include <vector>
51
52namespace score::gfx::interop
53{
54
57{
58 void* host{};
59 std::size_t bytes{};
60
65 std::uint32_t planeCount{0};
66 struct Plane
67 {
68 std::uint32_t offset{};
69 std::uint32_t pitch{};
70 } planes[3]{};
71};
72
74{
76 const char* tag, std::vector<BorrowedHostBuffer> buffers)
77 : m_name{std::string(tag ? tag : "?") + "-hostimport-borrowed"}
78 , m_buffers{std::move(buffers)}
79 {
80 }
81
82 const char* name() const noexcept override { return m_name.c_str(); }
83
84 bool init(const VideoCaptureStrategyConfig& c) override
85 {
86 cfg = c;
87 if(!cfg.rhi || !cfg.outputTexture || cfg.frameByteSize == 0)
88 return false;
89
90 // Planar layouts upload one plane per texture out of the one contiguous
91 // producer buffer. The split comes from the decoder's own plane textures,
92 // the same derivation CpuStagedCapture uses, so the rungs cannot disagree
93 // about where chroma begins.
94 m_planeUploads.clear();
95 const bool explicitLayout
96 = !m_buffers.empty() && m_buffers[0].planeCount >= cfg.planes.size();
97 if(cfg.planes.size() > 1)
98 {
99 std::size_t off = 0;
100 for(std::size_t i = 0; i < cfg.planes.size(); ++i)
101 {
102 auto* tex = cfg.planes[i];
103 if(!tex)
104 {
105 qDebug() << m_name.c_str() << ": decoder plane texture is null";
106 return false;
107 }
108 const auto psz = tex->pixelSize();
109 const auto bytes = std::size_t(psz.width()) * texelBytes(tex->format())
110 * std::size_t(psz.height());
111 const std::size_t planeOff
112 = explicitLayout ? m_buffers[0].planes[i].offset : off;
113 if(planeOff + bytes > cfg.frameByteSize)
114 {
115 qDebug() << m_name.c_str() << ": plane overruns the frame (" << planeOff
116 << "+" << bytes << ">" << cfg.frameByteSize << ")";
117 return false;
118 }
119 m_planeUploads.push_back(
120 PlaneUpload{tex, planeOff, psz.width(), psz.height()});
121 off = planeOff + bytes;
122 }
123 }
124
125 if(m_buffers.size() < 2)
126 {
127 qDebug() << m_name.c_str() << ": only" << m_buffers.size() << "buffers";
128 return false;
129 }
130 if(m_buffers.size() > BorrowedSlotTracker::kMaxSlots)
131 m_buffers.resize(BorrowedSlotTracker::kMaxSlots);
132
133 const std::size_t vkAlign = VkHostImportUpload::requiredAlignment(*cfg.rhi);
134 const std::size_t d3dAlign = D3D12HostImportUpload::requiredAlignment(*cfg.rhi);
135 const std::size_t align = vkAlign ? vkAlign : d3dAlign;
136 if(align == 0)
137 {
138 qDebug() << m_name.c_str()
139 << ": backend has no host-import path (needs Vulkan with "
140 "VK_EXT_external_memory_host, or D3D12)";
141 return false;
142 }
143
144 std::vector<void*> ptrs;
145 ptrs.reserve(m_buffers.size());
146 for(const auto& b : m_buffers)
147 {
148 if(!b.host || b.bytes < cfg.frameByteSize)
149 {
150 qDebug() << m_name.c_str() << ": buffer of" << b.bytes
151 << "cannot hold a frame of" << cfg.frameByteSize;
152 return false;
153 }
154 if(reinterpret_cast<std::uintptr_t>(b.host) % align != 0)
155 {
156 qDebug() << m_name.c_str() << ": buffer is not aligned to" << align;
157 return false;
158 }
159 ptrs.push_back(b.host);
160 }
161
162 m_tracker.retireDepth
163 = static_cast<std::size_t>(cfg.rhi->resourceLimit(QRhi::FramesInFlight))
164 + 1u;
165 // The producer needs buffers left to fill while the renderer holds its own.
166 if(m_tracker.retireDepth + 2u > m_buffers.size())
167 {
168 qDebug() << m_name.c_str() << ": producer ring of" << m_buffers.size()
169 << "is too shallow for" << m_tracker.retireDepth
170 << "frames in flight plus the device's own queue";
171 return false;
172 }
173
174 // The producer's stride; D3D12 builds its copy footprint from it.
175 const std::size_t rowPitch
176 = cfg.height > 0 ? cfg.frameByteSize / std::size_t(cfg.height) : 0;
177 // Vulkan needs it too, not only D3D12.
178 m_rowPitch = rowPitch;
179
180 const bool ok = vkAlign ? m_hostImport.init(*cfg.rhi, ptrs, cfg.frameByteSize)
181 : m_d3dImport.init(
182 *cfg.rhi, ptrs, cfg.frameByteSize, rowPitch);
183 if(!ok)
184 {
185 // Expected on drivers whose mmap does not hand back ordinary pinnable
186 // host pages: VK_EXT_external_memory_host can only import memory
187 // vkGetMemoryHostPointerPropertiesEXT accepts, and a mapping of the
188 // device's own DMA buffers frequently is not that.
189 qDebug() << m_name.c_str() << ": the producer's buffers cannot be imported "
190 "as host memory; staying on the CPU rung";
191 m_hostImport.release();
192 m_d3dImport.release();
193 return false;
194 }
195 m_tracker.reset();
196 qDebug() << m_name.c_str() << ": engaged over" << m_buffers.size()
197 << "producer-owned buffers (zero-copy)";
198 return true;
199 }
200
201 void release() override
202 {
203 m_hostImport.release();
204 m_d3dImport.release();
205 m_tracker.reset();
206 }
207
208 std::size_t slotCount() const noexcept override { return m_buffers.size(); }
209
210 void* slotBuffer(std::size_t i) const noexcept override
211 {
212 return i < m_buffers.size() ? m_buffers[i].host : nullptr;
213 }
214
216 bool ingestFrame(std::size_t i) override
217 {
218 if(i >= m_buffers.size())
219 return false;
220 m_tracker.ingest(i);
221 return true;
222 }
223
226 std::uint32_t takeReturnedSlots() noexcept { return m_tracker.takeReturned(); }
227
228 QRhiTexture* outputTexture() const noexcept override { return cfg.outputTexture; }
229
230 void acquireForRender() override { }
231 void acquireForRender(QRhiResourceUpdateBatch& res) override
232 {
233 acquireForRender(res, nullptr);
234 }
235
236 void acquireForRender(QRhiResourceUpdateBatch&, QRhiCommandBuffer* cb) override
237 {
238 if(!cb)
239 return;
240 const int slot = m_tracker.acquire();
241 if(slot < 0)
242 return;
243 const auto s = static_cast<std::size_t>(slot);
244 auto upload = [&](QRhiTexture& t, int w, int h, std::size_t off) {
245 // Single-plane only: a planar layout gives each plane its own stride, and
246 // one number cannot describe them, so those stay tightly packed.
247 const std::size_t pitch = m_planeUploads.empty() ? m_rowPitch : 0;
248 return m_hostImport.valid()
249 ? m_hostImport.copyToTexture(*cb, t, s, w, h, off, pitch)
250 : m_d3dImport.copyToTexture(*cb, t, s, w, h, off);
251 };
252
253 if(!m_planeUploads.empty())
254 {
255 for(const auto& p : m_planeUploads)
256 upload(*p.tex, p.width, p.height, p.offset);
257 return;
258 }
259
260 const auto sz = cfg.outputTexture->pixelSize();
261 upload(*cfg.outputTexture, sz.width(), sz.height(), 0);
262 }
263
264 void releaseAfterRender() override { }
265
266private:
269 std::size_t m_rowPitch{};
270
273 struct PlaneUpload
274 {
275 QRhiTexture* tex{};
276 std::size_t offset{};
277 int width{};
278 int height{};
279 };
280
284 static std::uint32_t texelBytes(QRhiTexture::Format f) noexcept
285 {
286 switch(f)
287 {
288 case QRhiTexture::R8:
289 case QRhiTexture::RED_OR_ALPHA8:
290 return 1;
291 case QRhiTexture::RG8:
292 case QRhiTexture::R16:
293 return 2;
294 case QRhiTexture::RG16:
295 return 4;
296 case QRhiTexture::RGBA8:
297 case QRhiTexture::BGRA8:
298 return 4;
299 default:
300 return 4;
301 }
302 }
303
304 std::vector<PlaneUpload> m_planeUploads;
305 std::string m_name;
306 std::vector<BorrowedHostBuffer> m_buffers;
307 VideoCaptureStrategyConfig cfg{};
308 VkHostImportUpload m_hostImport;
309 D3D12HostImportUpload m_d3dImport;
310 BorrowedSlotTracker m_tracker;
311};
312
313} // namespace score::gfx::interop
Backend-neutral plumbing shared by GPU-direct video CAPTURE strategies (no graphics-API headers).
Zero-copy CPU->GPU upload on D3D12, the counterpart of VkHostImportUpload.
Vendor-neutral interface for GPU-direct video CAPTURE strategies.
Zero-copy CPU->GPU upload on Vulkan via VK_EXT_external_memory_host.
bool copyToTexture(QRhiCommandBuffer &cb, QRhiTexture &tex, std::size_t slot, int width, int height, std::size_t srcOffset=0) noexcept
Definition D3D12HostImportUpload.cpp:274
static std::size_t requiredAlignment(QRhi &rhi) noexcept
Definition D3D12HostImportUpload.cpp:261
bool init(QRhi &rhi, const std::vector< void * > &slots, std::size_t bytes, std::size_t rowPitch)
Definition D3D12HostImportUpload.cpp:269
bool init(QRhi &rhi, const std::vector< void * > &slots, std::size_t bytes)
Definition VkHostImportUpload.cpp:469
bool copyToTexture(QRhiCommandBuffer &cb, QRhiTexture &tex, std::size_t slot, int width, int height, std::size_t srcOffset=0, std::size_t rowPitchBytes=0) noexcept
Definition VkHostImportUpload.cpp:474
static std::size_t requiredAlignment(QRhi &rhi) noexcept
Definition VkHostImportUpload.cpp:464
Definition BorrowedHostImportCapture.hpp:67
One producer-owned frame buffer.
Definition BorrowedHostImportCapture.hpp:57
std::uint32_t planeCount
Definition BorrowedHostImportCapture.hpp:65
Definition BorrowedHostImportCapture.hpp:74
void * slotBuffer(std::size_t i) const noexcept override
Definition BorrowedHostImportCapture.hpp:210
std::size_t slotCount() const noexcept override
Definition BorrowedHostImportCapture.hpp:208
bool ingestFrame(std::size_t i) override
Producer thread. Slot i becomes render-owned.
Definition BorrowedHostImportCapture.hpp:216
std::uint32_t takeReturnedSlots() noexcept
Definition BorrowedHostImportCapture.hpp:226
void acquireForRender(QRhiResourceUpdateBatch &res) override
Definition BorrowedHostImportCapture.hpp:231
const char * name() const noexcept override
Definition BorrowedHostImportCapture.hpp:82
void acquireForRender(QRhiResourceUpdateBatch &, QRhiCommandBuffer *cb) override
Definition BorrowedHostImportCapture.hpp:236
QRhiTexture * outputTexture() const noexcept override
Definition BorrowedHostImportCapture.hpp:228
void acquireForRender() override
Definition BorrowedHostImportCapture.hpp:230
int ingest(std::size_t i) noexcept
Definition CaptureStrategyCommon.hpp:83
std::uint32_t takeReturned() noexcept
Producer thread. Bitmask of slots the renderer has finished with.
Definition CaptureStrategyCommon.hpp:122
int acquire() noexcept
Render thread. The slot to bind now, or -1 when nothing is pending.
Definition CaptureStrategyCommon.hpp:95
std::size_t retireDepth
framesInFlight + 1, from QRhi::resourceLimit(QRhi::FramesInFlight).
Definition CaptureStrategyCommon.hpp:79
Definition VideoCaptureStrategy.hpp:43
std::vector< QRhiTexture * > planes
Definition VideoCaptureStrategy.hpp:71
QRhiTexture * outputTexture
Definition VideoCaptureStrategy.hpp:62
std::uint32_t frameByteSize
Definition VideoCaptureStrategy.hpp:49
Per-graphics-API strategy for getting a captured video frame into a GPU QRhiTexture without CPU stagi...
Definition VideoCaptureStrategy.hpp:84