VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalTextureStream.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Triple-buffered texture streaming for real-time data ingestion.
5//
6// Provides lock-free single-producer single-consumer texture rotation
7// for tear-free real-time intraoperative display of endoscope video
8// and simulation feeds.
9//
10// Uses dispatch_semaphore_t for GPU back-pressure, consistent with
11// MetalUniformRingBuffer. StorageModeShared + WriteCombined for
12// optimal Apple Silicon streaming throughput.
13//
14// Usage:
15// MetalTextureStream stream(device, {1920, 1080});
16//
17// // Producer (any thread):
18// auto* tex = stream.beginWrite();
19// stream.writeRegion(pixels, bytesPerRow, region);
20// stream.endWrite();
21//
22// // Consumer (render thread):
23// auto* readTex = stream.acquireForRead();
24// metalTexture->setExternalTexture(readTex);
25// // ... render ...
26// stream.endFrame(commandBuffer);
27//
28#pragma once
29
30#include <Metal/Metal.hpp>
31#include <dispatch/dispatch.h>
32#include <array>
33#include <atomic>
34#include <cstdint>
35
36namespace visutwin::canvas
37{
54 {
55 public:
56 static constexpr int kNumSlots = 3;
57
59 {
60 uint32_t width = 1920;
61 uint32_t height = 1080;
62 MTL::PixelFormat format = MTL::PixelFormatBGRA8Unorm;
63 bool writeCombined = true;
64 const char* label = "TextureStream";
65 };
66
67 MetalTextureStream(MTL::Device* device, const Descriptor& desc);
69
70 // Non-copyable, non-movable
75
76 // ── Producer API (producer thread) ─────────────────────────────
77
81 MTL::Texture* beginWrite();
82
86 void writeRegion(const void* data, size_t bytesPerRow,
87 MTL::Region region, uint32_t mipLevel = 0);
88
91 void endWrite();
92
96 void publishExternal(MTL::Texture* externalTexture);
97
98 // ── Consumer API (render thread) ───────────────────────────────
99
103 MTL::Texture* acquireForRead();
104
109 void endFrame(MTL::CommandBuffer* commandBuffer);
110
111 // ── Accessors ──────────────────────────────────────────────────
112
113 [[nodiscard]] uint32_t width() const { return _desc.width; }
114 [[nodiscard]] uint32_t height() const { return _desc.height; }
115 [[nodiscard]] MTL::PixelFormat format() const { return _desc.format; }
116
118 [[nodiscard]] uint64_t framesPublished() const { return _publishCount.load(std::memory_order_relaxed); }
119
121 [[nodiscard]] uint64_t framesDropped() const { return _dropCount.load(std::memory_order_relaxed); }
122
124 [[nodiscard]] bool hasNewFrame() const { return _hasPublished; }
125
126 private:
127 MTL::Device* _device;
128 Descriptor _desc;
129
130 std::array<MTL::Texture*, kNumSlots> _textures{};
131 dispatch_semaphore_t _slotSemaphore = nullptr;
132
133 // Atomic indices for lock-free SPSC rotation.
134 // Each index is in [0, kNumSlots). All three are always distinct.
135 std::atomic<int> _writeIndex{0};
136 std::atomic<int> _readyIndex{1};
137 std::atomic<int> _readIndex{2};
138
139 // External texture path (CVMetalTexture zero-copy).
140 // When non-null, acquireForRead() returns this instead of rotating.
141 std::atomic<MTL::Texture*> _externalReady{nullptr};
142
143 // Statistics
144 std::atomic<uint64_t> _publishCount{0};
145 std::atomic<uint64_t> _dropCount{0};
146
147 bool _hasPublished = false;
148 };
149
150} // namespace visutwin::canvas
MetalTextureStream(const MetalTextureStream &)=delete
MetalTextureStream(MTL::Device *device, const Descriptor &desc)
MetalTextureStream & operator=(MetalTextureStream &&)=delete
void endFrame(MTL::CommandBuffer *commandBuffer)
uint64_t framesDropped() const
Number of frames dropped (overwritten in Ready before GPU consumed them).
bool hasNewFrame() const
True if at least one frame has been published.
void publishExternal(MTL::Texture *externalTexture)
MetalTextureStream & operator=(const MetalTextureStream &)=delete
MetalTextureStream(MetalTextureStream &&)=delete
uint64_t framesPublished() const
Total number of frames published by the producer.
void writeRegion(const void *data, size_t bytesPerRow, MTL::Region region, uint32_t mipLevel=0)
bool writeCombined
CPU write-only optimization (bypass cache).