Overte C++ Documentation
VKTextureTransfer.h
1 //
2 // Created by Dr. Karol Suprynowicz on 18.05.2026.
3 // Copyright 2026 Overte e.V.
4 //
5 // Distributed under the Apache License, Version 2.0.
6 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7 //
8 
9 #pragma once
10 
11 #include "VKTextureTransfer.h"
12 
13 #include <QtCore/QThreadPool>
14 //#include <QtConcurrent>
15 
16 #include "VKShared.h"
17 #include "VKBackend.h"
18 //#include "gpu/gl/GLTexelFormat.h"
19 #include <thread>
20 
21 namespace gpu { namespace vk {
22 
23 template <typename T>
24 struct LessPairSecond {
25  bool operator()(const T& a, const T& b) { return a.second < b.second; }
26 };
27 
28 
29 using QueuePair = std::pair<TextureWeakPointer, float>;
30 
31 // Contains a priority sorted list of textures on which work is to be done over many frames
32 // Uses a weak pointer to the texture to avoid keeping it in scope if the client stops using it
33 using WorkQueue = std::priority_queue<QueuePair, std::vector<QueuePair>, LessPairSecond<QueuePair>>;
34 
35 struct GLFilterMode {
36  GLint minFilter;
37  GLint magFilter;
38 };
39 
52 class TransferJob {
53 public:
54  using Pointer = std::shared_ptr<TransferJob>;
55  using Queue = std::queue<Pointer>;
56  using Lambda = std::function<void(const TexturePointer&)>;
57 private:
58  Texture::PixelsPointer _mipData;
59  size_t _transferOffset{ 0 };
60  size_t _transferSize{ 0 };
61  uint16_t _sourceMip{ 0 };
62  bool _bufferingRequired{ true };
63  Lambda _transferLambda{ [](const TexturePointer&) {} };
64  Lambda _bufferingLambda{ [](const TexturePointer&) {} };
65 public:
66  TransferJob(const TransferJob& other) = delete;
67  TransferJob(uint16_t sourceMip, const std::function<void()>& transferLambda);
68  TransferJob(const Texture& texture, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0);
69  ~TransferJob();
70  const uint16_t& sourceMip() const { return _sourceMip; }
71  const size_t& size() const { return _transferSize; }
72  bool bufferingRequired() const { return _bufferingRequired; }
73  void buffer(const TexturePointer& texture) { _bufferingLambda(texture); }
74  void transfer(const TexturePointer& texture) { _transferLambda(texture); }
75 };
76 
77 using TransferJobPointer = std::shared_ptr<TransferJob>;
78 using TransferQueue = std::queue<TransferJobPointer>;
79 
80 // A map of weak texture pointers to queues of work to be done to transfer their data from the backing store to the GPU
81 using TransferMap = std::map<TextureWeakPointer, TransferQueue, std::owner_less<TextureWeakPointer>>;
82 
83 class VKTextureTransferEngine {
84 public:
85  using Pointer = std::shared_ptr<VKTextureTransferEngine>;
86 
87  virtual ~VKTextureTransferEngine() = default;
88 
91  void manageMemory();
92  void shutdown();
93 
96  bool allowCreate() const { return _frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME; }
99  void addMemoryManagedTexture(const TexturePointer& texturePointer);
100 
101 protected:
102  class TextureBufferThread : public QThread {
103  public:
104  TextureBufferThread(VKTextureTransferEngine& parent) : _parent(parent) {}
105 
106  protected:
107  void run() override;
108 
109  VKTextureTransferEngine& _parent;
110  };
111 
112  // Fetch all the currently active textures as strong pointers, while clearing the
113  // empty weak pointers out of _registeredTextures
114  std::vector<TexturePointer> getAllTextures();
115  void resetFrameTextureCreated() { _frameTexturesCreated = 0; }
116 
117  using ActiveTransferJob = std::pair<TexturePointer, TransferJobPointer>;
118  using ActiveTransferQueue = std::list<ActiveTransferJob>;
119 
120  void populateActiveBufferQueue();
121  bool processActiveBufferQueue();
122  void processTransferQueues();
123  void populateTransferQueue(const TexturePointer& texturePointer);
124  //void addToWorkQueue(const TexturePointer& texturePointer);
125  void updateMemoryPressure();
126 
127  void processDemotes(size_t relief, const std::vector<TexturePointer>& strongTextures);
128  void processPromotes();
129 
130 private:
131  static const size_t MAX_RESOURCE_TEXTURES_PER_FRAME{ 2 };
132  size_t _frameTexturesCreated{ 0 };
133  std::list<TextureWeakPointer> _registeredTextures;
134 
135  std::atomic<bool> _shutdown{ false };
136  // Contains a priority sorted list of weak texture pointers that have been determined to be eligible for additional allocation
137  // While the memory state is 'undersubscribed', items will be removed from this list and processed, allocating additional memory
138  // per frame
139  WorkQueue _promoteQueue;
140  // This queue contains jobs that will buffer data from the texture backing store (ideally a memory mapped KTX file)
141  // to a CPU memory buffer. This queue is populated on the main GPU thread, and drained on a dedicated thread.
142  // When an item on the _activeBufferQueue is completed it is put into the _activeTransferQueue
143  ActiveTransferQueue _activeBufferQueue;
144  // This queue contains jobs that will upload data from a CPU buffer into a GPU. This queue is populated on the background
145  // thread that process the _activeBufferQueue and drained on the main GPU thread
146  ActiveTransferQueue _activeTransferQueue;
147  // Mutex protecting the _activeTransferQueue & _activeBufferQueue since they are each accessed both from the main GPU thread
148  // and the buffering thread
149  Mutex _bufferMutex;
150  // The buffering thread which drains the _activeBufferQueue and populates the _activeTransferQueue
151  TextureBufferThread* _transferThread{ nullptr };
152  // The amount of buffering work currently represented by the _activeBufferQueue
153  std::atomic<size_t> _queuedBufferSize{ 0 };
154  // This contains a map of all textures to queues of pending transfer jobs. While in the transfer state, this map is used to
155  // populate the _activeBufferQueue up to the limit specified in GLVariableAllocationTexture::MAX_BUFFER_SIZE
156  TransferMap _pendingTransfersMap;
157 
158 };
159 
160 
161 class VKVariableAllocationSupport {
162  friend class VKBackend;
163 
164 public:
165  VKVariableAllocationSupport();
166  virtual ~VKVariableAllocationSupport() ;
167  virtual void populateTransferQueue(TransferQueue& pendingTransfers) = 0;
168 
169  void sanityCheck() const;
170  uint16 populatedMip() const { return _populatedMip; }
171  bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
172  bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
173  bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
174 
175  virtual size_t promote() = 0;
176  virtual size_t demote() = 0;
177 
178  static const uvec3 MAX_TRANSFER_DIMENSIONS;
179  static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS;
180  static const size_t MAX_TRANSFER_SIZE;
181  static const size_t MAX_BUFFER_SIZE;
182 
183 protected:
184  // THe amount of memory currently allocated
185  Size _size { 0 };
186 
187  // The amount of memory currently populated
188  void incrementPopulatedSize(Size delta) const;
189  void decrementPopulatedSize(Size delta) const;
190  mutable Size _populatedSize { 0 };
191 
192  // The allocated mip level, relative to the number of mips in the gpu::Texture object
193  // The relationship between a given glMip to the original gpu::Texture mip is always
194  // glMip + _allocatedMip
195  uint16 _allocatedMip { 0 };
196  // The populated mip level, relative to the number of mips in the gpu::Texture object
197  // This must always be >= the allocated mip
198  uint16 _populatedMip { 0 };
199  // The highest (lowest resolution) mip that we will support, relative to the number
200  // of mips in the gpu::Texture object
201  uint16 _maxAllocatedMip { 0 };
202  // The lowest (highest resolution) mip that we will support, relative to the number
203  // of mips in the gpu::Texture object
204  uint16 _minAllocatedMip { 0 };
205 };
206 
207 } }
Definition: VKTextureTransfer.h:52