Re-apply Vulkan zeta surface texture correctness fix

Track VkImageLayout on SurfaceBinding and transition correctly between
DEPTH_STENCIL_ATTACHMENT_OPTIMAL and DEPTH_STENCIL_READ_ONLY_OPTIMAL
when depth surfaces are used as textures.

- renderer.h: Add image_layout to SurfaceBinding; tex_surface_direct_layout[] to PGRAPHVkState
- image.c: Add DEPTH_STENCIL_READ_ONLY_OPTIMAL transition cases for TRANSFER_SRC/DST
- surface.c: Replace hardcoded layouts with image_layout tracking throughout
- draw.c: Barrier in begin_render_pass to restore zeta from READ_ONLY to ATTACHMENT
- shaders.c: Use tex_surface_direct_layout[] instead of hardcoded GENERAL
- texture.c: Add bind_zeta_surface_as_texture(); use image_layout in all s2t paths;
  direct-bind depth surfaces without stencil in DEPTH_STENCIL_READ_ONLY_OPTIMAL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
izzy2lost
2026-04-08 02:34:59 -04:00
co-authored by Claude Sonnet 4.6
parent 997c8d0b30
commit e71284cfbf
6 changed files with 177 additions and 17 deletions
+30
View File
@@ -1243,6 +1243,36 @@ static void begin_render_pass(PGRAPHState *pg)
assert(r->framebuffer_index > 0);
if (r->zeta_binding &&
r->zeta_binding->image_layout !=
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = r->zeta_binding->image_layout,
.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = r->zeta_binding->image,
.subresourceRange = {
.aspectMask = r->zeta_binding->host_fmt.aspect,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
};
vkCmdPipelineBarrier(
r->command_buffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
0, 0, NULL, 0, NULL, 1, &barrier);
r->zeta_binding->image_layout =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
}
VkRenderPassBeginInfo render_pass_begin_info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = r->render_pass,
+33
View File
@@ -202,6 +202,15 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd,
sourceStage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
// Depth Read-Only -> Src
} else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
// Depth -> Dst
} else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
@@ -210,6 +219,14 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd,
sourceStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
// Depth Read-Only -> Dst
} else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
// Src -> Color
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
@@ -226,6 +243,22 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd,
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
// Src -> Depth Read-Only
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
// Dst -> Depth Read-Only
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
// Src -> Dst
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+2
View File
@@ -136,6 +136,7 @@ typedef struct SurfaceBinding {
VkImage image;
VkImageView image_view;
VkImageLayout image_layout;
VmaAllocation allocation;
// Used for scaling
@@ -402,6 +403,7 @@ typedef struct PGRAPHVkState {
TextureBinding *texture_bindings[NV2A_MAX_TEXTURES];
bool tex_surface_direct[NV2A_MAX_TEXTURES];
VkImageView tex_surface_direct_views[NV2A_MAX_TEXTURES];
VkImageLayout tex_surface_direct_layout[NV2A_MAX_TEXTURES];
TextureBinding dummy_texture;
bool texture_bindings_changed;
VkFormatProperties *texture_format_properties;
+1 -1
View File
@@ -209,7 +209,7 @@ void pgraph_vk_update_descriptor_sets(PGRAPHState *pg)
for (int i = 0; i < NV2A_MAX_TEXTURES; i++) {
image_infos[i] = (VkDescriptorImageInfo){
.imageLayout = r->tex_surface_direct[i]
? VK_IMAGE_LAYOUT_GENERAL
? r->tex_surface_direct_layout[i]
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.imageView = r->tex_surface_direct[i]
? r->tex_surface_direct_views[i]
+16 -4
View File
@@ -207,9 +207,9 @@ static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface,
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
surface->image_layout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
int num_copy_regions = 1;
VkBufferImageCopy copy_regions[2];
@@ -319,6 +319,9 @@ static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
surface->image_layout =
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
// FIXME: Verify output of depth stencil conversion
// FIXME: Track current layout and only transition when required
@@ -889,6 +892,9 @@ static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface)
VK_IMAGE_LAYOUT_UNDEFINED,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
surface->image_layout =
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
nv2a_profile_inc_counter(NV2A_PROF_QUEUE_SUBMIT_3);
pgraph_vk_end_debug_marker(r, cmd);
@@ -900,6 +906,7 @@ static void migrate_surface_image(SurfaceBinding *dst, SurfaceBinding *src)
{
dst->image = src->image;
dst->image_view = src->image_view;
dst->image_layout = src->image_layout;
dst->allocation = src->allocation;
dst->image_scratch = src->image_scratch;
dst->image_scratch_current_layout = src->image_scratch_current_layout;
@@ -907,6 +914,7 @@ static void migrate_surface_image(SurfaceBinding *dst, SurfaceBinding *src)
src->image = VK_NULL_HANDLE;
src->image_view = VK_NULL_HANDLE;
src->image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
src->allocation = VK_NULL_HANDLE;
src->image_scratch = VK_NULL_HANDLE;
src->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -1286,9 +1294,9 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
surface->image_layout,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
bool upscale = pg->surface_scale_factor > 1 &&
!use_compute_to_convert_depth_stencil_format;
@@ -1341,6 +1349,9 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
surface->image_layout =
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
nv2a_profile_inc_counter(NV2A_PROF_QUEUE_SUBMIT_2);
pgraph_vk_end_debug_marker(r, cmd);
@@ -1443,6 +1454,7 @@ static void populate_surface_binding_target_sized(NV2AState *d, bool color,
target->frame_time = pg->frame_time;
target->draw_time = pg->draw_time;
target->cleared = false;
target->image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
target->initialized = false;
}
+95 -12
View File
@@ -756,19 +756,15 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
surface->image_layout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vkCmdCopyImageToBuffer(
cmd, surface->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dst_storage_buffer->buffer,
num_regions, regions);
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
VkBuffer texture_source_buffer;
if (use_compute_to_convert_depth_stencil) {
@@ -887,6 +883,12 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
texture->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
surface->image_layout,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
pgraph_vk_end_debug_marker(r, cmd);
pgraph_vk_end_nondraw_commands(pg, cmd);
@@ -976,6 +978,46 @@ static void bind_surface_as_texture(PGRAPHState *pg, SurfaceBinding *surface,
texture->draw_time = surface->draw_time;
}
static void bind_zeta_surface_as_texture(PGRAPHState *pg, SurfaceBinding *surface,
TextureBinding *texture)
{
assert(!surface->color);
assert(!(surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT));
nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX);
if (surface->image_layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) {
VkCommandBuffer cmd = pgraph_vk_begin_nondraw_commands(pg);
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = surface->image_layout,
.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = surface->image,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
};
vkCmdPipelineBarrier(
cmd, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 0, NULL, 0, NULL, 1, &barrier);
pgraph_vk_end_nondraw_commands(pg, cmd);
surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
}
texture->draw_time = surface->draw_time;
}
static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface,
TextureBinding *texture)
{
@@ -998,9 +1040,9 @@ static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface,
pgraph_vk_transition_image_layout(
pg, cmd, surface->image, surface->host_fmt.vk_format,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
surface->image_layout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
pgraph_vk_transition_image_layout(pg, cmd, texture->image, vkf.vk_format,
texture->current_layout,
@@ -1027,6 +1069,9 @@ static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
surface->image_layout =
surface->color ? VK_IMAGE_LAYOUT_GENERAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
pgraph_vk_transition_image_layout(pg, cmd, texture->image, vkf.vk_format,
texture->current_layout,
@@ -1291,6 +1336,8 @@ static void create_texture(PGRAPHState *pg, int texture_idx)
bool surface_to_texture = false;
r->tex_surface_direct[texture_idx] = false;
r->tex_surface_direct_views[texture_idx] = VK_NULL_HANDLE;
r->tex_surface_direct_layout[texture_idx] =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// Check active surfaces to see if this texture was a render target
SurfaceBinding *surface = pgraph_vk_surface_get(d, texture_vram_offset);
@@ -1399,12 +1446,29 @@ static void create_texture(PGRAPHState *pg, int texture_idx)
r->tex_surface_direct[texture_idx] = true;
r->tex_surface_direct_views[texture_idx] =
get_surface_direct_texture_view(r, surface, snode);
r->tex_surface_direct_layout[texture_idx] = VK_IMAGE_LAYOUT_GENERAL;
} else {
r->tex_surface_direct[texture_idx] = false;
r->tex_surface_direct_views[texture_idx] = VK_NULL_HANDLE;
r->tex_surface_direct_layout[texture_idx] =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
} else {
bool can_direct_bind =
!(surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT);
if (can_direct_bind) {
if (surface->draw_time != snode->draw_time ||
surface->image_layout !=
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) {
bind_zeta_surface_as_texture(pg, surface, snode);
}
r->tex_surface_direct[texture_idx] = true;
r->tex_surface_direct_views[texture_idx] = surface->image_view;
r->tex_surface_direct_layout[texture_idx] =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
} else if (surface->draw_time != snode->draw_time) {
copy_surface_to_texture(pg, surface, snode);
}
} else if (surface->draw_time != snode->draw_time) {
copy_surface_to_texture(pg, surface, snode);
}
} else {
if (possibly_dirty && content_hash != snode->hash) {
@@ -1619,12 +1683,25 @@ static void create_texture(PGRAPHState *pg, int texture_idx)
r->tex_surface_direct[texture_idx] = true;
r->tex_surface_direct_views[texture_idx] =
get_surface_direct_texture_view(r, surface, snode);
r->tex_surface_direct_layout[texture_idx] = VK_IMAGE_LAYOUT_GENERAL;
} else {
r->tex_surface_direct[texture_idx] = false;
r->tex_surface_direct_views[texture_idx] = VK_NULL_HANDLE;
r->tex_surface_direct_layout[texture_idx] =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
} else {
copy_surface_to_texture(pg, surface, snode);
bool can_direct_bind =
!(surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT);
if (can_direct_bind) {
bind_zeta_surface_as_texture(pg, surface, snode);
r->tex_surface_direct[texture_idx] = true;
r->tex_surface_direct_views[texture_idx] = surface->image_view;
r->tex_surface_direct_layout[texture_idx] =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
} else {
copy_surface_to_texture(pg, surface, snode);
}
}
} else {
upload_texture_image(pg, texture_idx, snode);
@@ -1687,6 +1764,8 @@ void pgraph_vk_bind_textures(NV2AState *d)
}
r->tex_surface_direct[i] = false;
r->tex_surface_direct_views[i] = VK_NULL_HANDLE;
r->tex_surface_direct_layout[i] =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
pg->texture_dirty[i] = false;
continue;
}
@@ -1701,12 +1780,14 @@ void pgraph_vk_bind_textures(NV2AState *d)
TextureBinding *prev_binding = r->texture_bindings[i];
bool prev_direct = r->tex_surface_direct[i];
VkImageView prev_direct_view = r->tex_surface_direct_views[i];
VkImageLayout prev_direct_layout = r->tex_surface_direct_layout[i];
create_texture(pg, i);
pg->texture_dirty[i] = false; // FIXME: Move to renderer?
if (r->texture_bindings[i] != prev_binding ||
r->tex_surface_direct[i] != prev_direct ||
r->tex_surface_direct_views[i] != prev_direct_view) {
r->tex_surface_direct_views[i] != prev_direct_view ||
r->tex_surface_direct_layout[i] != prev_direct_layout) {
r->texture_bindings_changed = true;
}
}
@@ -1845,6 +1926,8 @@ void pgraph_vk_finalize_textures(PGRAPHState *pg)
r->texture_bindings[i] = NULL;
r->tex_surface_direct[i] = false;
r->tex_surface_direct_views[i] = VK_NULL_HANDLE;
r->tex_surface_direct_layout[i] =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
destroy_dummy_texture(r);