@@ -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
211200RDD::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
638619void RenderingDeviceDriverMetal::texture_unmap (TextureID p_texture) {
0 commit comments