11#include <spdlog/spdlog.h>
18 MTL::PixelFormat pixelFormatToMetal(
const PixelFormat format)
22 return MTL::PixelFormatRGBA8Unorm;
24 return MTL::PixelFormatRGBA8Unorm;
26 return MTL::PixelFormatRGBA16Float;
28 return MTL::PixelFormatRGBA32Float;
30 return MTL::PixelFormatR32Float;
32 return MTL::PixelFormatR8Unorm;
34 return MTL::PixelFormatRG8Unorm;
36 return MTL::PixelFormatDepth16Unorm;
38 return MTL::PixelFormatDepth32Float;
40 return MTL::PixelFormatDepth32Float_Stencil8;
42 return MTL::PixelFormatRGBA8Unorm;
76 _descriptor->release();
77 _descriptor =
nullptr;
79 if (_metalTexture && _ownsTexture) {
80 _metalTexture->release();
81 _metalTexture =
nullptr;
87 if (_metalTexture && _ownsTexture) {
88 _metalTexture->release();
90 _metalTexture = externalTexture;
96 assert(_texture->width() > 0 && _texture->height() > 0);
98 _descriptor = MTL::TextureDescriptor::alloc()->init();
99 _descriptor->setPixelFormat(pixelFormatToMetal(_texture->format()));
100 _descriptor->setWidth(_texture->width());
101 _descriptor->setHeight(_texture->height());
102 _descriptor->setMipmapLevelCount(std::max(1u, _texture->getNumLevels()));
103 MTL::TextureUsage usage = MTL::TextureUsageShaderRead | MTL::TextureUsageRenderTarget;
104 if (_texture->storage()) {
105 usage = usage | MTL::TextureUsageShaderWrite;
107 _descriptor->setUsage(usage);
108 _descriptor->setStorageMode(MTL::StorageModeShared);
110 if (_texture->isCubemap()) {
111 _descriptor->setTextureType(MTL::TextureTypeCube);
112 }
else if (_texture->isVolume()) {
113 _descriptor->setTextureType(MTL::TextureType3D);
114 _descriptor->setDepth(_texture->depth());
117 _metalTexture = device->
raw()->newTexture(_descriptor);
126 (_metalTexture->width() != _texture->width() ||
127 _metalTexture->height() != _texture->height() ||
128 (_texture->isVolume() && _metalTexture->depth() != _texture->depth()))) {
131 if (_descriptor) { _descriptor->release(); _descriptor =
nullptr; }
132 _metalTexture->release(); _metalTexture =
nullptr;
135 _texture->setNeedsUpload(
false);
136 _texture->setNeedsMipmapsUpload(
false);
140 if (_texture->needsUpload() || _texture->needsMipmapsUpload())
143 _texture->setNeedsUpload(
false);
144 _texture->setNeedsMipmapsUpload(
false);
151 if (!_descriptor || !_metalTexture) {
152 spdlog::warn(
"Texture upload skipped: Metal texture is not initialized");
155 if (!_texture->hasLevels())
160 bool anyUploads =
false;
161 bool anyLevelMissing =
false;
162 uint32_t requiredMipLevels = _texture->getNumLevels();
164 for (uint32_t mipLevel = 0; mipLevel < requiredMipLevels; mipLevel++)
166 auto* mipObject = _texture->getLevel(mipLevel);
170 if (_texture->isCubemap())
173 for (uint32_t face = 0; face < 6; face++)
175 auto* faceSource = _texture->getFaceData(mipLevel, face);
178 const auto sourceSize = _texture->getLevelDataSize(mipLevel, face);
179 uploadRawImage(faceSource, sourceSize, mipLevel, face);
184 anyLevelMissing =
true;
188 else if (_texture->isVolume())
190 const auto sourceSize = _texture->getLevelDataSize(mipLevel);
191 uploadVolumeData(mipObject, sourceSize, mipLevel);
194 else if (_texture->isArray())
197 for (uint32_t index = 0; index < _texture->getArrayLength(); index++)
199 if (
auto* arraySource = _texture->getArrayData(mipLevel, index))
201 const auto sourceSize = _texture->getLevelDataSize(mipLevel);
202 uploadRawImage(arraySource, sourceSize, mipLevel, index);
210 const auto sourceSize = _texture->getLevelDataSize(mipLevel);
211 uploadRawImage(mipObject, sourceSize, mipLevel, 0);
217 anyLevelMissing =
true;
222 void MetalTexture::uploadRawImage(
void* imageData,
size_t imageDataSize, uint32_t mipLevel, uint32_t index)
const
227 const auto descriptorMipLevels = std::max(1u,
static_cast<uint32_t
>(_descriptor->mipmapLevelCount()));
228 if (mipLevel >= descriptorMipLevels) {
229 spdlog::warn(
"Texture upload skipped: mip level {} out of descriptor range {}", mipLevel, descriptorMipLevels);
233 const auto mipWidth = std::max(1u,
static_cast<uint32_t
>(_descriptor->width()) >> mipLevel);
234 const auto mipHeight = std::max(1u,
static_cast<uint32_t
>(_descriptor->height()) >> mipLevel);
235 const auto bpp = bytesPerPixel(_texture->
format());
236 const auto expectedSize =
static_cast<size_t>(mipWidth) *
static_cast<size_t>(mipHeight) * bpp;
237 if (imageDataSize > 0 && imageDataSize < expectedSize) {
238 spdlog::warn(
"Texture upload skipped: level data size {} smaller than expected {}", imageDataSize, expectedSize);
242 const MTL::Region region = MTL::Region(0, 0, 0, mipWidth, mipHeight, 1);
244 const NS::UInteger slice = _texture->
isCubemap() ? index : 0;
249 const size_t pixelCount =
static_cast<size_t>(mipWidth) *
static_cast<size_t>(mipHeight);
250 std::vector<uint8_t> expanded(pixelCount * 4u);
251 const auto* src =
static_cast<const uint8_t*
>(imageData);
252 for (
size_t i = 0; i < pixelCount; ++i) {
253 const size_t srcOffset = i * 3u;
254 const size_t dstOffset = i * 4u;
255 expanded[dstOffset + 0u] = src[srcOffset + 0u];
256 expanded[dstOffset + 1u] = src[srcOffset + 1u];
257 expanded[dstOffset + 2u] = src[srcOffset + 2u];
258 expanded[dstOffset + 3u] = 255u;
261 const NS::UInteger bytesPerRow = 4u * mipWidth;
262 _metalTexture->replaceRegion(region, mipLevel, slice, expanded.data(), bytesPerRow, 0);
266 const NS::UInteger bytesPerRow = bpp * mipWidth;
267 _metalTexture->replaceRegion(region, mipLevel, slice, imageData, bytesPerRow, 0);
270 void MetalTexture::uploadVolumeData(
void* imageData,
size_t imageDataSize, uint32_t mipLevel)
const
275 const auto descriptorMipLevels = std::max(1u,
static_cast<uint32_t
>(_descriptor->mipmapLevelCount()));
276 if (mipLevel >= descriptorMipLevels) {
277 spdlog::warn(
"Volume texture upload skipped: mip level {} out of descriptor range {}", mipLevel, descriptorMipLevels);
281 const auto mipWidth = std::max(1u,
static_cast<uint32_t
>(_descriptor->width()) >> mipLevel);
282 const auto mipHeight = std::max(1u,
static_cast<uint32_t
>(_descriptor->height()) >> mipLevel);
283 const auto mipDepth = std::max(1u,
static_cast<uint32_t
>(_descriptor->depth()) >> mipLevel);
284 const auto bpp = bytesPerPixel(_texture->format());
286 const auto expectedSize =
static_cast<size_t>(mipWidth) * mipHeight * mipDepth * bpp;
287 if (imageDataSize > 0 && imageDataSize < expectedSize) {
288 spdlog::warn(
"Volume texture upload skipped: data size {} < expected {}", imageDataSize, expectedSize);
292 const MTL::Region region = MTL::Region(0, 0, 0, mipWidth, mipHeight, mipDepth);
293 const NS::UInteger bytesPerRow =
static_cast<NS::UInteger
>(bpp) * mipWidth;
294 const NS::UInteger bytesPerImage = bytesPerRow * mipHeight;
296 _metalTexture->replaceRegion(region, mipLevel, 0, imageData, bytesPerRow, bytesPerImage);
302 for (
auto* sampler : _samplers) {
Abstract GPU interface for resource creation, state management, and draw submission.
GPU texture resource supporting 2D, cubemap, volume, and array formats with mipmap management.
PixelFormat format() const
void uploadImmediate(GraphicsDevice *device) override
void setExternalTexture(MTL::Texture *externalTexture)
MetalTexture(Texture *texture)
void uploadData(GraphicsDevice *device)
void create(MetalGraphicsDevice *device)
void propertyChanged(uint32_t flag) override
@ PIXELFORMAT_DEPTHSTENCIL