From bbcb6b6851337058af8df77b99a4e727a21d2b26 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 1 Nov 2017 01:36:39 +0300 Subject: [PATCH] rsx: Fbo fixes 2 - Use AA mode to predict surface compression. Compression mode is useless without AA activated - Rewrites most image subresource fetch routines to use the new heuristic - Fix rsx::thread::find_tile. FEED000(X) can be substituted for (X) in the code -- Fixes alot of failures when looking for tiled regions rsx: Fix antialiased unnormalized coords - scaling factors are inverse to allow proper coordinates to be computed in fs --- rpcs3/Emu/RSX/Common/surface_store.h | 63 +++++++----- rpcs3/Emu/RSX/Common/texture_cache.h | 147 ++++++++++++++++++++------- rpcs3/Emu/RSX/GL/GLRenderTargets.cpp | 17 +++- rpcs3/Emu/RSX/RSXThread.cpp | 6 +- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 3 + rpcs3/Emu/RSX/VK/VKRenderTargets.h | 1 + 6 files changed, 168 insertions(+), 69 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index f3400f2b9e..62309a0ce7 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -47,6 +47,9 @@ namespace rsx template struct render_target_descriptor { + GcmTileInfo *tile = nullptr; + rsx::surface_antialiasing aa_mode = rsx::surface_antialiasing::center_1_sample; + virtual image_storage_type get_surface() const = 0; virtual u16 get_surface_width() const = 0; virtual u16 get_surface_height() const = 0; @@ -584,7 +587,7 @@ namespace rsx * address_is_bound - returns true if the surface at a given address is actively bound * get_surface_subresource_if_available - returns a sectiion descriptor that allows to crop surfaces stored in memory */ - bool surface_overlaps_address(surface_type surface, u32 surface_address, u32 texaddr, u16 *x, u16 *y, bool scale_to_fit, bool double_height) + bool surface_overlaps_address(surface_type surface, u32 surface_address, u32 texaddr, u16 *x, u16 *y) { bool is_subslice = false; u16 x_offset = 0; @@ -605,24 +608,31 @@ namespace rsx surface_format_info info; Traits::get_surface_info(surface, &info); + bool doubled_x = false; + bool doubled_y = false; + + switch (surface->aa_mode) + { + case rsx::surface_antialiasing::square_rotated_4_samples: + case rsx::surface_antialiasing::square_centered_4_samples: + doubled_y = true; + //fall through + case rsx::surface_antialiasing::diagonal_centered_2_samples: + doubled_x = true; + break; + } + u32 range = info.rsx_pitch * info.surface_height; - if (double_height) range <<= 1; + if (doubled_y) range <<= 1; if (offset < range) { - const u32 y = (offset / info.rsx_pitch); - u32 x = (offset % info.rsx_pitch) / info.bpp; + y_offset = (offset / info.rsx_pitch); + x_offset = (offset % info.rsx_pitch) / info.bpp; - if (scale_to_fit) - { - const f32 x_scale = (f32)info.rsx_pitch / info.native_pitch; - x = (u32)((f32)x / x_scale); - } + if (doubled_x) x_offset /= 2; + if (doubled_y) y_offset /= 2; - x_offset = x; - y_offset = y; - - if (double_height) y_offset /= 2; is_subslice = true; } } @@ -677,27 +687,34 @@ namespace rsx } surface_subresource get_surface_subresource_if_applicable(u32 texaddr, u16 requested_width, u16 requested_height, u16 requested_pitch, - bool scale_to_fit = false, bool crop = false, bool ignore_depth_formats = false, bool ignore_color_formats = false, bool double_height = false) + bool crop = false, bool ignore_depth_formats = false, bool ignore_color_formats = false) { auto test_surface = [&](surface_type surface, u32 this_address, u16 &x_offset, u16 &y_offset, u16 &w, u16 &h, bool &clipped) { - if (surface_overlaps_address(surface, this_address, texaddr, &x_offset, &y_offset, scale_to_fit, double_height)) + if (surface_overlaps_address(surface, this_address, texaddr, &x_offset, &y_offset)) { surface_format_info info; Traits::get_surface_info(surface, &info); u16 real_width = requested_width; + u16 real_height = requested_height; - if (scale_to_fit) + switch (surface->aa_mode) { - f32 pitch_scaling = (f32)requested_pitch / info.native_pitch; - real_width = (u16)((f32)requested_width / pitch_scaling); + case rsx::surface_antialiasing::diagonal_centered_2_samples: + real_width /= 2; + break; + case rsx::surface_antialiasing::square_centered_4_samples: + case rsx::surface_antialiasing::square_rotated_4_samples: + real_width /= 2; + real_height /= 2; + break; } - if (region_fits(info.surface_width, info.surface_height, x_offset, y_offset, real_width, requested_height)) + if (region_fits(info.surface_width, info.surface_height, x_offset, y_offset, real_width, real_height)) { w = real_width; - h = requested_height; + h = real_height; clipped = false; return true; @@ -710,17 +727,17 @@ namespace rsx u16 remaining_height = info.surface_height - y_offset; w = std::min(real_width, remaining_width); - h = std::min(requested_height, remaining_height); + h = std::min(real_height, remaining_height); clipped = true; return true; } - if (info.surface_width >= real_width && info.surface_height >= requested_height) + if (info.surface_width >= real_width && info.surface_height >= real_height) { LOG_WARNING(RSX, "Overlapping surface exceeds bounds; returning full surface region"); w = real_width; - h = requested_height; + h = real_height; clipped = true; return true; diff --git a/rpcs3/Emu/RSX/Common/texture_cache.h b/rpcs3/Emu/RSX/Common/texture_cache.h index dfebf620ac..47637b8b35 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache.h +++ b/rpcs3/Emu/RSX/Common/texture_cache.h @@ -30,7 +30,8 @@ namespace rsx { texture_upload_context upload_context = texture_upload_context::shader_read; bool is_depth_texture = false; - f32 internal_scale = 1.f; + f32 scale_x = 1.f; + f32 scale_y = 1.f; }; struct cached_texture_section : public rsx::buffered_section @@ -219,12 +220,13 @@ namespace rsx sampled_image_descriptor() {} - sampled_image_descriptor(image_view_type handle, const texture_upload_context ctx, const bool is_depth, const f32 scale) + sampled_image_descriptor(image_view_type handle, const texture_upload_context ctx, const bool is_depth, const f32 x_scale, const f32 y_scale) { image_handle = handle; upload_context = ctx; is_depth_texture = is_depth; - internal_scale = scale; + scale_x = x_scale; + scale_y = y_scale; } }; @@ -447,7 +449,7 @@ namespace rsx return {}; } - bool is_hw_blit_engine_compatible(const u32 format) const + inline bool is_hw_blit_engine_compatible(const u32 format) const { switch (format) { @@ -461,6 +463,80 @@ namespace rsx } } + /** + * Scaling helpers + * - get_native_dimensions() returns w and h for the native texture given rsx dimensions + * on rsx a 512x512 texture with 4x AA is treated as a 1024x1024 texture for example + * - get_rsx_dimensions() inverse, return rsx w and h given a real texture w and h + * - get_internal_scaling_x/y() returns a scaling factor to be multiplied by 1/size + * when sampling with unnormalized coordinates. tcoords passed to rsx will be in rsx dimensions + */ + template + inline void get_native_dimensions(T &width, T &height, T rsx_pitch, U surface) + { + switch (surface->aa_mode) + { + case rsx::surface_antialiasing::center_1_sample: + return; + case rsx::surface_antialiasing::diagonal_centered_2_samples: + width /= 2; + return; + case rsx::surface_antialiasing::square_centered_4_samples: + case rsx::surface_antialiasing::square_rotated_4_samples: + width /= 2; + height /= 2; + return; + } + } + + template + inline void get_rsx_dimensions(T &width, T &height, T rsx_pitch, U surface) + { + switch (surface->aa_mode) + { + case rsx::surface_antialiasing::center_1_sample: + return; + case rsx::surface_antialiasing::diagonal_centered_2_samples: + width *= 2; + return; + case rsx::surface_antialiasing::square_centered_4_samples: + case rsx::surface_antialiasing::square_rotated_4_samples: + width *= 2; + height *= 2; + return; + } + } + + template + inline f32 get_internal_scaling_x(T surface) + { + switch (surface->aa_mode) + { + default: + case rsx::surface_antialiasing::center_1_sample: + return 1.f; + case rsx::surface_antialiasing::diagonal_centered_2_samples: + case rsx::surface_antialiasing::square_centered_4_samples: + case rsx::surface_antialiasing::square_rotated_4_samples: + return 0.5f; + } + } + + template + inline f32 get_internal_scaling_y(T surface) + { + switch (surface->aa_mode) + { + default: + case rsx::surface_antialiasing::center_1_sample: + case rsx::surface_antialiasing::diagonal_centered_2_samples: + return 1.f; + case rsx::surface_antialiasing::square_centered_4_samples: + case rsx::surface_antialiasing::square_rotated_4_samples: + return 0.5f; + } + } + public: texture_cache() {} @@ -894,10 +970,11 @@ namespace rsx if (extended_dimension != rsx::texture_dimension_extended::texture_dimension_2d) LOG_ERROR(RSX, "Texture resides in render target memory, but requested type is not 2D (%d)", (u32)extended_dimension); - const f32 internal_scale = (f32)texptr->get_native_pitch() / tex.pitch(); - const u32 internal_width = tex_width * texptr->get_native_pitch() / tex.pitch(); + u32 internal_width = tex_width; + u32 internal_height = tex_height; + get_native_dimensions(internal_width, internal_height, (u32)tex_pitch, texptr); - bool requires_processing = texptr->get_surface_width() != internal_width || texptr->get_surface_height() != tex_height; + bool requires_processing = texptr->get_surface_width() != internal_width || texptr->get_surface_height() != internal_height; if (!requires_processing) { for (const auto& tex : m_rtts.m_bound_render_targets) @@ -923,11 +1000,12 @@ namespace rsx if (requires_processing) { const auto w = rsx::apply_resolution_scale(internal_width, true); - const auto h = rsx::apply_resolution_scale(tex_height, true); - return{ create_temporary_subresource_view(cmd, texptr, format, 0, 0, w, h), texture_upload_context::framebuffer_storage, false, internal_scale }; + const auto h = rsx::apply_resolution_scale(internal_height, true); + return{ create_temporary_subresource_view(cmd, texptr, format, 0, 0, w, h), texture_upload_context::framebuffer_storage, + false, get_internal_scaling_x(texptr), get_internal_scaling_y(texptr) }; } - return{ texptr->get_view(), texture_upload_context::framebuffer_storage, false, internal_scale }; + return{ texptr->get_view(), texture_upload_context::framebuffer_storage, false, get_internal_scaling_x(texptr), get_internal_scaling_y(texptr) }; } else { @@ -943,10 +1021,11 @@ namespace rsx if (extended_dimension != rsx::texture_dimension_extended::texture_dimension_2d) LOG_ERROR(RSX, "Texture resides in depth buffer memory, but requested type is not 2D (%d)", (u32)extended_dimension); - const f32 internal_scale = (f32)texptr->get_native_pitch() / tex.pitch(); - const u32 internal_width = tex_width * texptr->get_native_pitch() / tex.pitch(); + u32 internal_width = tex_width; + u32 internal_height = tex_height; + get_native_dimensions(internal_width, internal_height, (u32)tex_pitch, texptr); - bool requires_processing = texptr->get_surface_width() != internal_width || texptr->get_surface_height() != tex_height; + bool requires_processing = texptr->get_surface_width() != internal_width || texptr->get_surface_height() != internal_height; if (!requires_processing && texaddr == std::get<0>(m_rtts.m_bound_depth_stencil)) { if (g_cfg.video.strict_rendering_mode) @@ -964,11 +1043,12 @@ namespace rsx if (requires_processing) { const auto w = rsx::apply_resolution_scale(internal_width, true); - const auto h = rsx::apply_resolution_scale(tex_height, true); - return{ create_temporary_subresource_view(cmd, texptr, format, 0, 0, w, h), texture_upload_context::framebuffer_storage, true, internal_scale }; + const auto h = rsx::apply_resolution_scale(internal_height, true); + return{ create_temporary_subresource_view(cmd, texptr, format, 0, 0, w, h), texture_upload_context::framebuffer_storage, + true, get_internal_scaling_x(texptr), get_internal_scaling_y(texptr) }; } - return{ texptr->get_view(), texture_upload_context::framebuffer_storage, true, internal_scale }; + return{ texptr->get_view(), texture_upload_context::framebuffer_storage, true, get_internal_scaling_x(texptr), get_internal_scaling_y(texptr) }; } else { @@ -1008,11 +1088,7 @@ namespace rsx */ //TODO: Take framebuffer Y compression into account - const u32 native_pitch = tex_width * get_format_block_size_in_bytes(format); - const f32 internal_scale = (f32)tex_pitch / native_pitch; - const u32 internal_width = (const u32)(tex_width * internal_scale); - - const auto rsc = m_rtts.get_surface_subresource_if_applicable(texaddr, internal_width, tex_height, tex_pitch, true); + const auto rsc = m_rtts.get_surface_subresource_if_applicable(texaddr, tex_width, tex_height, tex_pitch); if (rsc.surface) { //TODO: Check that this region is not cpu-dirty before doing a copy @@ -1038,16 +1114,18 @@ namespace rsx insert_texture_barrier(); } - return{ rsc.surface->get_view(), texture_upload_context::framebuffer_storage, rsc.is_depth_surface, 1.f }; + return{ rsc.surface->get_view(), texture_upload_context::framebuffer_storage, rsc.is_depth_surface, get_internal_scaling_x(rsc.surface), get_internal_scaling_y(rsc.surface) }; } else return{ create_temporary_subresource_view(cmd, rsc.surface, format, rsx::apply_resolution_scale(rsc.x, false), rsx::apply_resolution_scale(rsc.y, false), - rsx::apply_resolution_scale(rsc.w, true), rsx::apply_resolution_scale(rsc.h, true)), texture_upload_context::framebuffer_storage, rsc.is_depth_surface, 1.f }; + rsx::apply_resolution_scale(rsc.w, true), rsx::apply_resolution_scale(rsc.h, true)), texture_upload_context::framebuffer_storage, + rsc.is_depth_surface, get_internal_scaling_x(rsc.surface), get_internal_scaling_y(rsc.surface) }; } else { LOG_WARNING(RSX, "Attempting to sample a currently bound render target @ 0x%x", texaddr); return{ create_temporary_subresource_view(cmd, rsc.surface, format, rsx::apply_resolution_scale(rsc.x, false), rsx::apply_resolution_scale(rsc.y, false), - rsx::apply_resolution_scale(rsc.w, true), rsx::apply_resolution_scale(rsc.h, true)), texture_upload_context::framebuffer_storage, rsc.is_depth_surface, 1.f }; + rsx::apply_resolution_scale(rsc.w, true), rsx::apply_resolution_scale(rsc.h, true)), texture_upload_context::framebuffer_storage, + rsc.is_depth_surface, get_internal_scaling_x(rsc.surface), get_internal_scaling_y(rsc.surface) }; } } } @@ -1060,7 +1138,7 @@ namespace rsx auto cached_texture = find_texture_from_dimensions(texaddr, tex_width, tex_height, depth); if (cached_texture) { - return{ cached_texture->get_raw_view(), cached_texture->get_context(), cached_texture->is_depth_texture(), 1.f }; + return{ cached_texture->get_raw_view(), cached_texture->get_context(), cached_texture->is_depth_texture(), 1.f, 1.f }; } if ((!blit_engine_incompatibility_warning_raised && g_cfg.video.use_gpu_texture_scaling) || is_hw_blit_engine_compatible(format)) @@ -1103,7 +1181,7 @@ namespace rsx auto src_image = surface->get_raw_texture(); if (auto result = create_temporary_subresource_view(cmd, &src_image, format, offset_x, offset_y, tex_width, tex_height)) - return{ result, texture_upload_context::blit_engine_dst, surface->is_depth_texture(), 1.f }; + return{ result, texture_upload_context::blit_engine_dst, surface->is_depth_texture(), 1.f, 1.f }; } } } @@ -1123,7 +1201,7 @@ namespace rsx m_texture_memory_in_use += (tex_pitch * tex_height); return{ upload_image_from_cpu(cmd, texaddr, tex_width, tex_height, depth, tex.get_exact_mipmap_count(), tex_pitch, format, texture_upload_context::shader_read, subresources_layout, extended_dimension, is_swizzled, remap_vector)->get_raw_view(), - texture_upload_context::shader_read, false, 1.f }; + texture_upload_context::shader_read, false, 1.f, 1.f }; } template @@ -1148,13 +1226,6 @@ namespace rsx float scale_x = dst.scale_x; float scale_y = dst.scale_y; - //TODO: Investigate effects of compression in X axis - if (dst.compressed_y) - scale_y *= 0.5f; - - if (src.compressed_y) - scale_y *= 2.f; - //Offset in x and y for src is 0 (it is already accounted for when getting pixels_src) //Reproject final clip onto source... const u16 src_w = (const u16)((f32)dst.clip_width / scale_x); @@ -1172,7 +1243,7 @@ namespace rsx } //Check if src/dst are parts of render targets - auto dst_subres = m_rtts.get_surface_subresource_if_applicable(dst_address, dst.width, dst.clip_height, dst.pitch, true, true, false, false, dst.compressed_y); + auto dst_subres = m_rtts.get_surface_subresource_if_applicable(dst_address, dst.width, dst.clip_height, dst.pitch, true, false, false); dst_is_render_target = dst_subres.surface != nullptr; if (dst_is_render_target && dst_subres.surface->get_native_pitch() != dst.pitch) @@ -1184,7 +1255,7 @@ namespace rsx } //TODO: Handle cases where src or dst can be a depth texture while the other is a color texture - requires a render pass to emulate - auto src_subres = m_rtts.get_surface_subresource_if_applicable(framebuffer_src_address, src_w, src_h, src.pitch, true, true, false, false, src.compressed_y); + auto src_subres = m_rtts.get_surface_subresource_if_applicable(framebuffer_src_address, src_w, src_h, src.pitch, true, false, false); src_is_render_target = src_subres.surface != nullptr; if (src_is_render_target && src_subres.surface->get_native_pitch() != src.pitch) @@ -1358,9 +1429,7 @@ namespace rsx if (src_subres.w != dst.clip_width || src_subres.h != dst.clip_height) { - f32 subres_scaling_x = (f32)src.pitch / src_subres.surface->get_native_pitch(); - - const int dst_width = (int)(src_subres.w * scale_x * subres_scaling_x); + const int dst_width = (int)(src_subres.w * scale_x); const int dst_height = (int)(src_subres.h * scale_y); dst_area.x2 = dst_area.x1 + dst_width; diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index e864ce7c53..1ea5477518 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -202,6 +202,10 @@ void GLGSRender::init_buffers(bool skip_reading) bool old_format_found = false; gl::texture::format old_format; + const auto color_offsets = get_offsets(); + const auto color_locations = get_locations(); + const auto aa_mode = rsx::method_registers.surface_antialias(); + for (int i = 0; i < rsx::limits::color_buffers_count; ++i) { if (surface_info[i].pitch && g_cfg.video.write_color_buffers) @@ -217,9 +221,10 @@ void GLGSRender::init_buffers(bool skip_reading) if (std::get<0>(m_rtts.m_bound_render_targets[i])) { - __glcheck draw_fbo.color[i] = *std::get<1>(m_rtts.m_bound_render_targets[i]); + auto rtt = std::get<1>(m_rtts.m_bound_render_targets[i]); + draw_fbo.color[i] = *rtt; - std::get<1>(m_rtts.m_bound_render_targets[i])->set_rsx_pitch(pitchs[i]); + rtt->set_rsx_pitch(pitchs[i]); surface_info[i] = { surface_addresses[i], pitchs[i], false, surface_format, depth_format, clip_horizontal, clip_vertical }; //Verify pitch given is correct if pitch <= 64 (especially 64) @@ -237,6 +242,8 @@ void GLGSRender::init_buffers(bool skip_reading) } } + rtt->tile = find_tile(color_offsets[i], color_locations[i]); + rtt->aa_mode = aa_mode; m_gl_texture_cache.tag_framebuffer(surface_addresses[i]); } else @@ -245,10 +252,11 @@ void GLGSRender::init_buffers(bool skip_reading) if (std::get<0>(m_rtts.m_bound_depth_stencil)) { + auto ds = std::get<1>(m_rtts.m_bound_depth_stencil); if (depth_format == rsx::surface_depth_format::z24s8) - __glcheck draw_fbo.depth_stencil = *std::get<1>(m_rtts.m_bound_depth_stencil); + draw_fbo.depth_stencil = *ds; else - __glcheck draw_fbo.depth = *std::get<1>(m_rtts.m_bound_depth_stencil); + draw_fbo.depth = *ds; const u32 depth_surface_pitch = rsx::method_registers.surface_z_pitch(); std::get<1>(m_rtts.m_bound_depth_stencil)->set_rsx_pitch(rsx::method_registers.surface_z_pitch()); @@ -269,6 +277,7 @@ void GLGSRender::init_buffers(bool skip_reading) } } + ds->aa_mode = aa_mode; m_gl_texture_cache.tag_framebuffer(depth_address); } else diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 0965e9314b..8d31730db4 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1387,8 +1387,8 @@ namespace rsx { if (raw_format & CELL_GCM_TEXTURE_UN) { - result.texture_scale[i][0] = (resolution_scale * sampler_descriptors[i]->internal_scale); - result.texture_scale[i][1] = resolution_scale; + result.texture_scale[i][0] = (resolution_scale * sampler_descriptors[i]->scale_x); + result.texture_scale[i][1] = (resolution_scale * sampler_descriptors[i]->scale_y); } } @@ -1562,7 +1562,7 @@ namespace rsx { for (GcmTileInfo &tile : tiles) { - if (!tile.binded || tile.location != location) + if (!tile.binded || (tile.location & 1) != (location & 1)) { continue; } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index c2712a0181..1b4a42634f 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2320,6 +2320,7 @@ void VKGSRender::prepare_rtts() const auto fbo_width = rsx::apply_resolution_scale(clip_width, true); const auto fbo_height = rsx::apply_resolution_scale(clip_height, true); + const auto aa_mode = rsx::method_registers.surface_antialias(); if (m_draw_fbo) { @@ -2404,6 +2405,7 @@ void VKGSRender::prepare_rtts() m_surface_info[index].pitch = 0; } + surface->aa_mode = aa_mode; m_texture_cache.tag_framebuffer(surface_addresses[index]); } @@ -2419,6 +2421,7 @@ void VKGSRender::prepare_rtts() if (m_depth_surface_info.pitch <= 64 && clip_width > m_depth_surface_info.pitch) m_depth_surface_info.pitch = 0; + ds->aa_mode = aa_mode; m_texture_cache.tag_framebuffer(zeta_address); } diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.h b/rpcs3/Emu/RSX/VK/VKRenderTargets.h index c971245c89..775f746994 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.h +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.h @@ -268,6 +268,7 @@ namespace rsx void notify_surface_invalidated(const std::unique_ptr &surface) { surface->frame_tag = vk::get_current_frame_id(); + if (!surface->frame_tag) surface->frame_tag = 1; } static bool rtt_has_format_width_height(const std::unique_ptr &rtt, surface_color_format format, size_t width, size_t height, bool check_refs=false)