Skip to content

Commit 71031e4

Browse files
committed
Merge pull request godotengine#108422 from stuartcarnie/108115_metal_cpu_read_bit
Metal: Fix `texture_get_data` other linear formats
2 parents 710a6e0 + a281e91 commit 71031e4

9 files changed

+240
-178
lines changed

drivers/d3d12/rendering_device_driver_d3d12.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,78 @@ void RenderingDeviceDriverD3D12::texture_get_copyable_layout(TextureID p_texture
18671867
r_layout->layer_pitch = subresource_total_size / tex_info->desc.ArraySize();
18681868
}
18691869

1870+
Vector<uint8_t> RenderingDeviceDriverD3D12::texture_get_data(TextureID p_texture, uint32_t p_layer) {
1871+
const TextureInfo *tex = (const TextureInfo *)p_texture.id;
1872+
1873+
DataFormat tex_format = tex->format;
1874+
uint32_t tex_width = tex->desc.Width;
1875+
uint32_t tex_height = tex->desc.Height;
1876+
uint32_t tex_depth = tex->desc.DepthOrArraySize;
1877+
uint32_t tex_mipmaps = tex->mipmaps;
1878+
1879+
uint32_t width, height, depth;
1880+
uint32_t tight_mip_size = get_image_format_required_size(tex_format, tex_width, tex_height, tex_depth, tex_mipmaps, &width, &height, &depth);
1881+
1882+
Vector<uint8_t> image_data;
1883+
image_data.resize(tight_mip_size);
1884+
1885+
uint32_t blockw, blockh;
1886+
get_compressed_image_format_block_dimensions(tex_format, blockw, blockh);
1887+
uint32_t block_size = get_compressed_image_format_block_byte_size(tex_format);
1888+
uint32_t pixel_size = get_image_format_pixel_size(tex_format);
1889+
1890+
{
1891+
uint8_t *w = image_data.ptrw();
1892+
1893+
uint32_t mipmap_offset = 0;
1894+
for (uint32_t mm_i = 0; mm_i < tex_mipmaps; mm_i++) {
1895+
uint32_t image_total = get_image_format_required_size(tex_format, tex_width, tex_height, tex_depth, mm_i + 1, &width, &height, &depth);
1896+
1897+
uint8_t *write_ptr_mipmap = w + mipmap_offset;
1898+
tight_mip_size = image_total - mipmap_offset;
1899+
1900+
RDD::TextureSubresource subres;
1901+
subres.aspect = RDD::TEXTURE_ASPECT_COLOR;
1902+
subres.layer = p_layer;
1903+
subres.mipmap = mm_i;
1904+
RDD::TextureCopyableLayout layout;
1905+
texture_get_copyable_layout(p_texture, subres, &layout);
1906+
1907+
uint8_t *img_mem = texture_map(p_texture, subres);
1908+
ERR_FAIL_NULL_V(img_mem, Vector<uint8_t>());
1909+
1910+
for (uint32_t z = 0; z < depth; z++) {
1911+
uint8_t *write_ptr = write_ptr_mipmap + z * tight_mip_size / depth;
1912+
const uint8_t *slice_read_ptr = img_mem + z * layout.depth_pitch;
1913+
1914+
if (block_size > 1) {
1915+
// Compressed.
1916+
uint32_t line_width = (block_size * (width / blockw));
1917+
for (uint32_t y = 0; y < height / blockh; y++) {
1918+
const uint8_t *rptr = slice_read_ptr + y * layout.row_pitch;
1919+
uint8_t *wptr = write_ptr + y * line_width;
1920+
1921+
memcpy(wptr, rptr, line_width);
1922+
}
1923+
} else {
1924+
// Uncompressed.
1925+
for (uint32_t y = 0; y < height; y++) {
1926+
const uint8_t *rptr = slice_read_ptr + y * layout.row_pitch;
1927+
uint8_t *wptr = write_ptr + y * pixel_size * width;
1928+
memcpy(wptr, rptr, (uint64_t)pixel_size * width);
1929+
}
1930+
}
1931+
}
1932+
1933+
texture_unmap(p_texture);
1934+
1935+
mipmap_offset = image_total;
1936+
}
1937+
}
1938+
1939+
return image_data;
1940+
}
1941+
18701942
uint8_t *RenderingDeviceDriverD3D12::texture_map(TextureID p_texture, const TextureSubresource &p_subresource) {
18711943
TextureInfo *tex_info = (TextureInfo *)p_texture.id;
18721944
#ifdef DEBUG_ENABLED

drivers/d3d12/rendering_device_driver_d3d12.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {
384384
virtual void texture_free(TextureID p_texture) override final;
385385
virtual uint64_t texture_get_allocation_size(TextureID p_texture) override final;
386386
virtual void texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) override final;
387+
virtual Vector<uint8_t> texture_get_data(TextureID p_texture, uint32_t p_layer) override final;
387388
virtual uint8_t *texture_map(TextureID p_texture, const TextureSubresource &p_subresource) override final;
388389
virtual void texture_unmap(TextureID p_texture) override final;
389390
virtual BitField<TextureUsageBits> texture_get_usages_supported_by_format(DataFormat p_format, bool p_cpu_readable) override final;

drivers/metal/rendering_device_driver_metal.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingDeviceDriverMet
113113

114114
private:
115115
// Returns true if the texture is a valid linear format.
116-
Result<bool> is_valid_linear(TextureFormat const &p_format) const;
117-
void _get_sub_resource(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) const;
116+
bool is_valid_linear(TextureFormat const &p_format) const;
118117

119118
public:
120119
virtual TextureID texture_create(const TextureFormat &p_format, const TextureView &p_view) override final;
@@ -124,6 +123,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingDeviceDriverMet
124123
virtual void texture_free(TextureID p_texture) override final;
125124
virtual uint64_t texture_get_allocation_size(TextureID p_texture) override final;
126125
virtual void texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) override final;
126+
virtual Vector<uint8_t> texture_get_data(TextureID p_texture, uint32_t p_layer) override final;
127127
virtual uint8_t *texture_map(TextureID p_texture, const TextureSubresource &p_subresource) override final;
128128
virtual void texture_unmap(TextureID p_texture) override final;
129129
virtual BitField<TextureUsageBits> texture_get_usages_supported_by_format(DataFormat p_format, bool p_cpu_readable) override final;

drivers/metal/rendering_device_driver_metal.mm

Lines changed: 89 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -187,25 +187,14 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve
187187
MTLTextureTypeCubeArray,
188188
};
189189

190-
RenderingDeviceDriverMetal::Result<bool> RenderingDeviceDriverMetal::is_valid_linear(TextureFormat const &p_format) const {
191-
if (!flags::any(p_format.usage_bits, TEXTURE_USAGE_CPU_READ_BIT)) {
192-
return false;
193-
}
194-
195-
PixelFormats &pf = *pixel_formats;
196-
MTLFormatType ft = pf.getFormatType(p_format.format);
197-
198-
// Requesting a linear format, which has further restrictions, similar to Vulkan
199-
// when specifying VK_IMAGE_TILING_LINEAR.
200-
201-
ERR_FAIL_COND_V_MSG(p_format.texture_type != TEXTURE_TYPE_2D, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must be 2D");
202-
ERR_FAIL_COND_V_MSG(ft != MTLFormatType::DepthStencil, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must not be a depth/stencil format");
203-
ERR_FAIL_COND_V_MSG(ft != MTLFormatType::Compressed, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must not be a compressed format");
204-
ERR_FAIL_COND_V_MSG(p_format.mipmaps != 1, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must have 1 mipmap level");
205-
ERR_FAIL_COND_V_MSG(p_format.array_layers != 1, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must have 1 array layer");
206-
ERR_FAIL_COND_V_MSG(p_format.samples != TEXTURE_SAMPLES_1, ERR_CANT_CREATE, "Linear (TEXTURE_USAGE_CPU_READ_BIT) textures must have 1 sample");
190+
bool RenderingDeviceDriverMetal::is_valid_linear(TextureFormat const &p_format) const {
191+
MTLFormatType ft = pixel_formats->getFormatType(p_format.format);
207192

208-
return true;
193+
return p_format.texture_type == TEXTURE_TYPE_2D // Linear textures must be 2D textures.
194+
&& ft != MTLFormatType::DepthStencil && ft != MTLFormatType::Compressed // Linear textures must not be depth/stencil or compressed formats.)
195+
&& p_format.mipmaps == 1 // Linear textures must have 1 mipmap level.
196+
&& p_format.array_layers == 1 // Linear textures must have 1 array layer.
197+
&& p_format.samples == TEXTURE_SAMPLES_1; // Linear textures must have 1 sample.
209198
}
210199

211200
RDD::TextureID RenderingDeviceDriverMetal::texture_create(const TextureFormat &p_format, const TextureView &p_view) {
@@ -292,6 +281,7 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve
292281
// Usage.
293282

294283
MTLResourceOptions options = 0;
284+
bool is_linear = false;
295285
#if defined(VISIONOS_ENABLED)
296286
const bool supports_memoryless = true;
297287
#else
@@ -304,6 +294,11 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve
304294
options = MTLResourceCPUCacheModeDefaultCache | MTLResourceHazardTrackingModeTracked;
305295
if (p_format.usage_bits & TEXTURE_USAGE_CPU_READ_BIT) {
306296
options |= MTLResourceStorageModeShared;
297+
// The user has indicated they want to read from the texture on the CPU,
298+
// so we'll see if we can use a linear format.
299+
// A linear format is a texture that is backed by a buffer,
300+
// which allows for CPU access to the texture data via a pointer.
301+
is_linear = is_valid_linear(p_format);
307302
} else {
308303
options |= MTLResourceStorageModePrivate;
309304
}
@@ -358,13 +353,6 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve
358353

359354
// Allocate memory.
360355

361-
bool is_linear;
362-
{
363-
Result<bool> is_linear_or_err = is_valid_linear(p_format);
364-
ERR_FAIL_COND_V(std::holds_alternative<Error>(is_linear_or_err), TextureID());
365-
is_linear = std::get<bool>(is_linear_or_err);
366-
}
367-
368356
id<MTLTexture> obj = nil;
369357
if (is_linear) {
370358
// Linear textures are restricted to 2D textures, a single mipmap level and a single array layer.
@@ -525,114 +513,107 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve
525513
return obj.allocatedSize;
526514
}
527515

528-
void RenderingDeviceDriverMetal::_get_sub_resource(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) const {
516+
void RenderingDeviceDriverMetal::texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) {
529517
id<MTLTexture> obj = rid::get(p_texture);
530-
531518
*r_layout = {};
532519

533520
PixelFormats &pf = *pixel_formats;
521+
DataFormat format = pf.getDataFormat(obj.pixelFormat);
534522

535-
size_t row_alignment = get_texel_buffer_alignment_for_format(obj.pixelFormat);
536-
size_t offset = 0;
537-
size_t array_layers = obj.arrayLength;
538-
MTLSize size = MTLSizeMake(obj.width, obj.height, obj.depth);
539-
MTLPixelFormat pixel_format = obj.pixelFormat;
523+
MTLSize sz = MTLSizeMake(obj.width, obj.height, obj.depth);
540524

541-
// First skip over the mipmap levels.
542-
for (uint32_t mipLvl = 0; mipLvl < p_subresource.mipmap; mipLvl++) {
543-
MTLSize mip_size = mipmapLevelSizeFromSize(size, mipLvl);
544-
size_t bytes_per_row = pf.getBytesPerRow(pixel_format, mip_size.width);
545-
bytes_per_row = round_up_to_alignment(bytes_per_row, row_alignment);
546-
size_t bytes_per_layer = pf.getBytesPerLayer(pixel_format, bytes_per_row, mip_size.height);
547-
offset += bytes_per_layer * mip_size.depth * array_layers;
525+
if (p_subresource.mipmap > 0) {
526+
r_layout->offset = get_image_format_required_size(format, sz.width, sz.height, sz.depth, p_subresource.mipmap);
548527
}
549528

550-
// Get current mipmap.
551-
MTLSize mip_size = mipmapLevelSizeFromSize(size, p_subresource.mipmap);
552-
size_t bytes_per_row = pf.getBytesPerRow(pixel_format, mip_size.width);
553-
bytes_per_row = round_up_to_alignment(bytes_per_row, row_alignment);
554-
size_t bytes_per_layer = pf.getBytesPerLayer(pixel_format, bytes_per_row, mip_size.height);
555-
r_layout->size = bytes_per_layer * mip_size.depth;
556-
r_layout->offset = offset + (r_layout->size * p_subresource.layer - 1);
557-
r_layout->depth_pitch = bytes_per_layer;
558-
r_layout->row_pitch = bytes_per_row;
559-
r_layout->layer_pitch = r_layout->size * array_layers;
529+
sz = mipmapLevelSizeFromSize(sz, p_subresource.mipmap);
530+
531+
uint32_t bw = 0, bh = 0;
532+
get_compressed_image_format_block_dimensions(format, bw, bh);
533+
uint32_t sbw = 0, sbh = 0;
534+
r_layout->size = get_image_format_required_size(format, sz.width, sz.height, sz.depth, 1, &sbw, &sbh);
535+
r_layout->row_pitch = r_layout->size / ((sbh / bh) * sz.depth);
536+
r_layout->depth_pitch = r_layout->size / sz.depth;
537+
538+
uint32_t array_length = obj.arrayLength;
539+
if (obj.textureType == MTLTextureTypeCube) {
540+
array_length = 6;
541+
} else if (obj.textureType == MTLTextureTypeCubeArray) {
542+
array_length *= 6;
543+
}
544+
r_layout->layer_pitch = r_layout->size / array_length;
560545
}
561546

562-
void RenderingDeviceDriverMetal::texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) {
547+
Vector<uint8_t> RenderingDeviceDriverMetal::texture_get_data(TextureID p_texture, uint32_t p_layer) {
563548
id<MTLTexture> obj = rid::get(p_texture);
564-
*r_layout = {};
549+
ERR_FAIL_COND_V_MSG(obj.storageMode != MTLStorageModeShared, Vector<uint8_t>(), "Texture must be created with TEXTURE_USAGE_CPU_READ_BIT set.");
565550

566-
if ((obj.resourceOptions & MTLResourceStorageModePrivate) != 0) {
567-
MTLSize sz = MTLSizeMake(obj.width, obj.height, obj.depth);
551+
if (obj.buffer) {
552+
ERR_FAIL_COND_V_MSG(p_layer > 0, Vector<uint8_t>(), "A linear texture has a single layer.");
553+
ERR_FAIL_COND_V_MSG(obj.mipmapLevelCount > 1, Vector<uint8_t>(), "A linear texture has a single mipmap level.");
554+
Vector<uint8_t> image_data;
555+
image_data.resize_uninitialized(obj.buffer.length);
556+
memcpy(image_data.ptrw(), obj.buffer.contents, obj.buffer.length);
557+
return image_data;
558+
}
568559

569-
PixelFormats &pf = *pixel_formats;
570-
DataFormat format = pf.getDataFormat(obj.pixelFormat);
571-
if (p_subresource.mipmap > 0) {
572-
r_layout->offset = get_image_format_required_size(format, sz.width, sz.height, sz.depth, p_subresource.mipmap);
573-
}
560+
DataFormat tex_format = pixel_formats->getDataFormat(obj.pixelFormat);
561+
uint32_t tex_w = obj.width;
562+
uint32_t tex_h = obj.height;
563+
uint32_t tex_d = obj.depth;
564+
uint32_t tex_mipmaps = obj.mipmapLevelCount;
574565

575-
sz = mipmapLevelSizeFromSize(sz, p_subresource.mipmap);
566+
// Must iteratively copy the texture data to a buffer.
576567

577-
uint32_t bw = 0, bh = 0;
578-
get_compressed_image_format_block_dimensions(format, bw, bh);
579-
uint32_t sbw = 0, sbh = 0;
580-
r_layout->size = get_image_format_required_size(format, sz.width, sz.height, sz.depth, 1, &sbw, &sbh);
581-
r_layout->row_pitch = r_layout->size / ((sbh / bh) * sz.depth);
582-
r_layout->depth_pitch = r_layout->size / sz.depth;
568+
uint32_t tight_mip_size = get_image_format_required_size(tex_format, tex_w, tex_h, tex_d, tex_mipmaps);
583569

584-
uint32_t array_length = obj.arrayLength;
585-
if (obj.textureType == MTLTextureTypeCube) {
586-
array_length = 6;
587-
} else if (obj.textureType == MTLTextureTypeCubeArray) {
588-
array_length *= 6;
589-
}
590-
r_layout->layer_pitch = r_layout->size / array_length;
591-
} else {
592-
CRASH_NOW_MSG("need to calculate layout for shared texture");
593-
}
594-
}
570+
Vector<uint8_t> image_data;
571+
image_data.resize(tight_mip_size);
595572

596-
uint8_t *RenderingDeviceDriverMetal::texture_map(TextureID p_texture, const TextureSubresource &p_subresource) {
597-
id<MTLTexture> obj = rid::get(p_texture);
598-
ERR_FAIL_NULL_V_MSG(obj.buffer, nullptr, "texture is not created from a buffer");
573+
uint32_t pixel_size = get_image_format_pixel_size(tex_format);
574+
uint32_t pixel_rshift = get_compressed_image_format_pixel_rshift(tex_format);
575+
uint32_t blockw = 0, blockh = 0;
576+
get_compressed_image_format_block_dimensions(tex_format, blockw, blockh);
599577

600-
TextureCopyableLayout layout;
601-
_get_sub_resource(p_texture, p_subresource, &layout);
602-
return (uint8_t *)(obj.buffer.contents) + layout.offset;
603-
PixelFormats &pf = *pixel_formats;
578+
uint8_t *dest_ptr = image_data.ptrw();
604579

605-
size_t row_alignment = get_texel_buffer_alignment_for_format(obj.pixelFormat);
606-
size_t offset = 0;
607-
size_t array_layers = obj.arrayLength;
608-
MTLSize size = MTLSizeMake(obj.width, obj.height, obj.depth);
609-
MTLPixelFormat pixel_format = obj.pixelFormat;
580+
for (uint32_t mm_i = 0; mm_i < tex_mipmaps; mm_i++) {
581+
uint32_t bw = STEPIFY(tex_w, blockw);
582+
uint32_t bh = STEPIFY(tex_h, blockh);
610583

611-
// First skip over the mipmap levels.
612-
for (uint32_t mipLvl = 0; mipLvl < p_subresource.mipmap; mipLvl++) {
613-
MTLSize mipExtent = mipmapLevelSizeFromSize(size, mipLvl);
614-
size_t bytes_per_row = pf.getBytesPerRow(pixel_format, mipExtent.width);
615-
bytes_per_row = round_up_to_alignment(bytes_per_row, row_alignment);
616-
size_t bytes_per_layer = pf.getBytesPerLayer(pixel_format, bytes_per_row, mipExtent.height);
617-
offset += bytes_per_layer * mipExtent.depth * array_layers;
618-
}
584+
uint32_t bytes_per_row = (bw * pixel_size) >> pixel_rshift;
585+
uint32_t bytes_per_img = bytes_per_row * bh;
586+
uint32_t mip_size = bytes_per_img * tex_d;
619587

620-
if (p_subresource.layer > 1) {
621-
// Calculate offset to desired layer.
622-
MTLSize mipExtent = mipmapLevelSizeFromSize(size, p_subresource.mipmap);
623-
size_t bytes_per_row = pf.getBytesPerRow(pixel_format, mipExtent.width);
624-
bytes_per_row = round_up_to_alignment(bytes_per_row, row_alignment);
625-
size_t bytes_per_layer = pf.getBytesPerLayer(pixel_format, bytes_per_row, mipExtent.height);
626-
offset += bytes_per_layer * mipExtent.depth * (p_subresource.layer - 1);
588+
[obj getBytes:(void *)dest_ptr
589+
bytesPerRow:bytes_per_row
590+
bytesPerImage:bytes_per_img
591+
fromRegion:MTLRegionMake3D(0, 0, 0, bw, bh, tex_d)
592+
mipmapLevel:mm_i
593+
slice:p_layer];
594+
595+
dest_ptr += mip_size;
596+
597+
// Next mipmap level.
598+
tex_w = MAX(blockw, tex_w >> 1);
599+
tex_h = MAX(blockh, tex_h >> 1);
600+
tex_d = MAX(1u, tex_d >> 1);
627601
}
628602

629-
// TODO: Confirm with rendering team that there is no other way Godot may attempt to map a texture with multiple mipmaps or array layers.
603+
// Ensure that the destination pointer is at the end of the image data.
604+
DEV_ASSERT(dest_ptr - image_data.ptr() == image_data.size());
630605

631-
// NOTE: It is not possible to create a buffer-backed texture with mipmaps or array layers,
632-
// as noted in the is_valid_linear function, so the offset calculation SHOULD always be zero.
633-
// Given that, this code should be simplified.
606+
return image_data;
607+
}
608+
609+
uint8_t *RenderingDeviceDriverMetal::texture_map(TextureID p_texture, const TextureSubresource &p_subresource) {
610+
id<MTLTexture> obj = rid::get(p_texture);
611+
ERR_FAIL_COND_V_MSG(obj.storageMode != MTLStorageModeShared, nullptr, "Texture must be created with TEXTURE_USAGE_CPU_READ_BIT set.");
612+
ERR_FAIL_COND_V_MSG(obj.buffer, nullptr, "Texture mapping is not supported for non-linear textures in Metal.");
613+
ERR_FAIL_COND_V_MSG(p_subresource.layer > 0, nullptr, "A linear texture should have a single layer.");
614+
ERR_FAIL_COND_V_MSG(p_subresource.mipmap > 0, nullptr, "A linear texture should have a single mipmap.");
634615

635-
return (uint8_t *)(obj.buffer.contents) + offset;
616+
return (uint8_t *)obj.buffer.contents;
636617
}
637618

638619
void RenderingDeviceDriverMetal::texture_unmap(TextureID p_texture) {

0 commit comments

Comments
 (0)