diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index 2a5eacd535..6061849e4c 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -129,6 +129,7 @@ namespace rsx rsx::texture_dimension_extended image_type = texture_dimension_extended::texture_dimension_2d; rsx::format_class format_class = RSX_FORMAT_CLASS_UNDEFINED; bool is_cyclic_reference = false; + u8 samples = 1; u32 ref_address = 0; u64 surface_cache_tag = 0; f32 scale_x = 1.f; diff --git a/rpcs3/Emu/RSX/Common/texture_cache.h b/rpcs3/Emu/RSX/Common/texture_cache.h index ac1f5e2632..0e7f1ad736 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache.h +++ b/rpcs3/Emu/RSX/Common/texture_cache.h @@ -172,7 +172,8 @@ namespace rsx sampled_image_descriptor() = default; sampled_image_descriptor(image_view_type handle, texture_upload_context ctx, rsx::format_class ftype, - size3f scale, rsx::texture_dimension_extended type, bool cyclic_reference = false) + size3f scale, rsx::texture_dimension_extended type, bool cyclic_reference = false, + u8 msaa_samples = 1) { image_handle = handle; upload_context = ctx; @@ -182,6 +183,7 @@ namespace rsx scale_y = scale.height; scale_z = scale.depth; image_type = type; + samples = msaa_samples; } sampled_image_descriptor(image_resource_type external_handle, deferred_request_command reason, @@ -1888,7 +1890,7 @@ namespace rsx } auto result = helpers::merge_cache_resources( - overlapping_fbos, overlapping_locals, attr, scale, extended_dimension, encoded_remap, remap, _pool); + cmd, overlapping_fbos, overlapping_locals, attr, scale, extended_dimension, encoded_remap, remap, _pool); if (options.skip_texture_merge) { diff --git a/rpcs3/Emu/RSX/Common/texture_cache_helpers.h b/rpcs3/Emu/RSX/Common/texture_cache_helpers.h index feadec3786..b342374b64 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache_helpers.h +++ b/rpcs3/Emu/RSX/Common/texture_cache_helpers.h @@ -228,8 +228,9 @@ namespace rsx return { false, 0, dst_dimensions.width, dst_dimensions.height }; } - template + template void gather_texture_slices( + commandbuffer_type& cmd, std::vector& out, const surface_store_list_type& fbos, const std::vector& local, @@ -311,9 +312,11 @@ namespace rsx std::tie(src_x, src_y) = rsx::apply_resolution_scale(src_x, src_y, surface_width, surface_height); std::tie(dst_x, dst_y) = rsx::apply_resolution_scale(dst_x, dst_y, attr.width, attr.height); + section.surface->memory_barrier(cmd, rsx::surface_access::transfer_read); + out.push_back ({ - section.surface->get_surface(rsx::surface_access::shader_read), + section.surface->get_surface(rsx::surface_access::transfer_read), surface_transform::identity, 0, static_cast(src_x), @@ -505,8 +508,6 @@ namespace rsx bool surface_is_rop_target, bool force_convert) { - texptr->read_barrier(cmd); - const auto surface_width = texptr->template get_surface_width(); const auto surface_height = texptr->template get_surface_height(); @@ -553,26 +554,50 @@ namespace rsx ensure(attr.height == 1); } - if ((surface_is_rop_target && g_cfg.video.strict_rendering_mode) || - attr.width < surface_width || - attr.height < surface_height || - force_convert) + bool requires_processing = false; + rsx::surface_access access_type = rsx::surface_access::shader_read; + + if (attr.width != surface_width || attr.height != surface_height || force_convert) + { + // A GPU operation must be performed on the data before sampling. Implies transfer_read access + requires_processing = true; + } + else if (surface_is_rop_target && g_cfg.video.strict_rendering_mode) + { + // Framebuffer feedback avoidance. For MSAA, we do not need to make copies; just use the resolve target + if (texptr->samples() == 1) + { + requires_processing = true; + } + else + { + // Select resolve target instead of MSAA image + access_type = rsx::surface_access::transfer_read; + } + } + + if (requires_processing) { const auto format_class = (force_convert) ? classify_format(attr2.gcm_format) : texptr->format_class(); const auto command = surface_is_rop_target ? deferred_request_command::copy_image_dynamic : deferred_request_command::copy_image_static; - return { texptr->get_surface(rsx::surface_access::shader_read), command, attr2, {}, + texptr->memory_barrier(cmd, rsx::surface_access::transfer_read); + return { texptr->get_surface(rsx::surface_access::transfer_read), command, attr2, {}, texture_upload_context::framebuffer_storage, format_class, scale, extended_dimension, decoded_remap }; } - return{ texptr->get_view(encoded_remap, decoded_remap), texture_upload_context::framebuffer_storage, - texptr->format_class(), scale, rsx::texture_dimension_extended::texture_dimension_2d, surface_is_rop_target }; + texptr->memory_barrier(cmd, access_type); + auto viewed_surface = texptr->get_surface(access_type); + return { viewed_surface->get_view(encoded_remap, decoded_remap), texture_upload_context::framebuffer_storage, + texptr->format_class(), scale, rsx::texture_dimension_extended::texture_dimension_2d, surface_is_rop_target, viewed_surface->samples() }; } + texptr->memory_barrier(cmd, rsx::surface_access::transfer_read); + if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d) { - return{ texptr->get_surface(rsx::surface_access::shader_read), deferred_request_command::_3d_unwrap, + return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::_3d_unwrap, attr2, {}, texture_upload_context::framebuffer_storage, texptr->format_class(), scale, rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap }; @@ -580,14 +605,15 @@ namespace rsx ensure(extended_dimension == rsx::texture_dimension_extended::texture_dimension_cubemap); - return{ texptr->get_surface(rsx::surface_access::shader_read), deferred_request_command::cubemap_unwrap, + return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::cubemap_unwrap, attr2, {}, texture_upload_context::framebuffer_storage, texptr->format_class(), scale, rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap }; } - template + template sampled_image_descriptor merge_cache_resources( + commandbuffer_type& cmd, const surface_store_list_type& fbos, const std::vector& local, const image_section_attributes_t& attr, const size3f& scale, @@ -664,7 +690,7 @@ namespace rsx upload_context, format_class, scale, rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap }; - gather_texture_slices(desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth); + gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth); return desc; } else if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d && attr.depth > 1) @@ -677,7 +703,7 @@ namespace rsx upload_context, format_class, scale, rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap }; - gather_texture_slices(desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth); + gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth); return desc; } @@ -696,7 +722,7 @@ namespace rsx attr2, {}, upload_context, format_class, scale, rsx::texture_dimension_extended::texture_dimension_2d, decoded_remap }; - gather_texture_slices(result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth); + gather_texture_slices(cmd, result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth); result.simplify(); return result; } diff --git a/rpcs3/Emu/RSX/Common/time.hpp b/rpcs3/Emu/RSX/Common/time.hpp index 9cefd2e6c8..5faed311f5 100644 --- a/rpcs3/Emu/RSX/Common/time.hpp +++ b/rpcs3/Emu/RSX/Common/time.hpp @@ -3,7 +3,7 @@ #include #include -extern u64 get_system_time(); +#include "Emu/Cell/timers.hpp" namespace rsx { diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 3ef9e4e833..ec9dae8197 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -15,7 +15,7 @@ u64 GLGSRender::get_cycles() GLGSRender::GLGSRender() : GSRender() { - m_shaders_cache = std::make_unique(m_prog_buffer, "opengl", "v1.92"); + m_shaders_cache = std::make_unique(m_prog_buffer, "opengl", "v1.93"); if (g_cfg.video.disable_vertex_cache || g_cfg.video.multithreaded_rsx) m_vertex_cache = std::make_unique(); diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.h b/rpcs3/Emu/RSX/GL/GLHelpers.h index a6bcc2d880..a4a8ba43bd 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.h +++ b/rpcs3/Emu/RSX/GL/GLHelpers.h @@ -1594,6 +1594,11 @@ namespace gl return m_pitch; } + constexpr GLubyte samples() const + { + return 1; + } + GLboolean compressed() const { return m_compressed; diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index b7942ebba0..d16855bd8c 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -28,7 +28,7 @@ gl::texture* GLGSRender::get_present_source(gl::present_surface_info* info, cons gl::command_context cmd = { gl_state }; const auto format_bpp = rsx::get_format_block_size_in_bytes(info->format); const auto overlap_info = m_rtts.get_merged_texture_memory_region(cmd, - info->address, info->width, info->height, info->pitch, format_bpp, rsx::surface_access::shader_read); + info->address, info->width, info->height, info->pitch, format_bpp, rsx::surface_access::transfer_read); if (!overlap_info.empty()) { @@ -61,8 +61,7 @@ gl::texture* GLGSRender::get_present_source(gl::present_surface_info* info, cons if (viable) { - surface->read_barrier(cmd); - image = section.surface->get_surface(rsx::surface_access::shader_read); + image = section.surface->get_surface(rsx::surface_access::transfer_read); std::tie(info->width, info->height) = rsx::apply_resolution_scale( std::min(surface_width, info->width), diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.h b/rpcs3/Emu/RSX/GL/GLRenderTargets.h index e664981101..503494a0f6 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.h +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.h @@ -84,10 +84,10 @@ namespace gl static_cast(t)->release(); } - texture* get_surface(rsx::surface_access /*access_type*/) override + viewable_image* get_surface(rsx::surface_access /*access_type*/) override { // TODO - return static_cast(this); + return static_cast(this); } u32 raw_handle() const diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 673bdff0c7..6a9d0c8f9d 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1101,9 +1101,19 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode) ensure(func_id <= FUNCTION::TEXTURE_SAMPLE_MAX_BASE_ENUM && func_id >= FUNCTION::TEXTURE_SAMPLE_BASE); - // Clamp type to 3 types (1d, 2d, cube+3d) and offset into sampling redirection table - const auto type_offset = (std::min(static_cast(type), 2) + 1) * static_cast(FUNCTION::TEXTURE_SAMPLE_BASE_ENUM_COUNT); - func_id = static_cast(static_cast(func_id) + type_offset); + if (!(m_prog.texture_state.multisampled_textures & ref_mask)) [[ likely ]] + { + // Clamp type to 3 types (1d, 2d, cube+3d) and offset into sampling redirection table + const auto type_offset = (std::min(static_cast(type), 2) + 1) * static_cast(FUNCTION::TEXTURE_SAMPLE_BASE_ENUM_COUNT); + func_id = static_cast(static_cast(func_id) + type_offset); + } + else + { + // Map to multisample op + ensure(type <= rsx::texture_dimension_extended::texture_dimension_2d); + properties.multisampled_sampler_mask |= ref_mask; + func_id = static_cast(static_cast(func_id) - static_cast(FUNCTION::TEXTURE_SAMPLE_BASE) + static_cast(FUNCTION::TEXTURE_SAMPLE2DMS)); + } if (dst.exp_tex) { diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h index e55f6fc42d..c0be1a5fca 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h @@ -275,6 +275,7 @@ public: u16 common_access_sampler_mask = 0; u16 shadow_sampler_mask = 0; u16 redirected_sampler_mask = 0; + u16 multisampled_sampler_mask = 0; bool has_lit_op = false; bool has_gather_op = false; diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index 5404a9ac73..280e4fdb74 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -591,7 +591,10 @@ namespace glsl "#define ALPHAKILL " << rsx::texture_control_bits::ALPHAKILL << "\n" "#define RENORMALIZE " << rsx::texture_control_bits::RENORMALIZE << "\n" - "#define DEPTH_FLOAT " << rsx::texture_control_bits::DEPTH_FLOAT << "\n" + "#define DEPTH_FLOAT " << rsx::texture_control_bits::DEPTH_FLOAT << "\n" + "#define DEPTH_COMPARE " << rsx::texture_control_bits::DEPTH_COMPARE_OP << "\n" + "#define FILTERED_BIT " << rsx::texture_control_bits::FILTERED << "\n" + "#define INT_COORDS_BIT " << rsx::texture_control_bits::UNNORMALIZED_COORDS << "\n" "#define GAMMA_CTRL_MASK (GAMMA_R_MASK|GAMMA_G_MASK|GAMMA_B_MASK|GAMMA_A_MASK)\n" "#define SIGN_EXPAND_MASK (EXPAND_R_MASK|EXPAND_G_MASK|EXPAND_B_MASK|EXPAND_A_MASK)\n\n"; } @@ -887,6 +890,40 @@ namespace glsl "#define TEX2D_Z24X8_RGBA8(index, coord2) process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE2(index, coord2)), texture_parameters[index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))\n" "#define TEX3D_Z24X8_RGBA8(index, coord3) process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE3(index, coord3)), texture_parameters[index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))\n\n"; } + + if (props.require_msaa_ops) + { + OS << + "#define ZCOMPARE_FUNC(index) _get_bits(TEX_FLAGS(index), DEPTH_COMPARE, 3)\n" + "#define ZS_READ_MS(index, coord) vec2(sampleTexture2DMS(TEX_NAME(index), coord, index).r, float(sampleTexture2DMS(TEX_NAME_STENCIL(index), coord, index).x))\n" + "#define TEX2D_MS(index, coord2) process_texel(sampleTexture2DMS(TEX_NAME(index), coord2, index), TEX_FLAGS(index))\n" + "#define TEX2D_SHADOW_MS(index, coord3) vec4(comparison_passes(sampleTexture2DMS(TEX_NAME(index), coord3.xy, index).x, coord3.z, ZCOMPARE_FUNC(index)))\n" + "#define TEX2D_SHADOWPROJ_MS(index, coord4) TEX2D_SHADOW_MS(index, (coord4.xyz / coord4.w))\n" + "#define TEX2D_Z24X8_RGBA8_MS(index, coord2) process_texel(convert_z24x8_to_rgba8(ZS_READ_MS(index, coord2), texture_parameters[index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))\n\n"; + + auto insert_msaa_sample_code = [&OS](const std::string_view& sampler_type) + { + OS << + "vec4 sampleTexture2DMS(in " << sampler_type << " tex, const in vec2 coords, const in int index)\n" + "{\n" + " const uint flags = TEX_FLAGS(index);\n" + " const ivec2 sample_count = ivec2(2, textureSamples(tex) / 2);\n" + " const ivec2 icoords = ivec2(COORD_SCALE2(index, coords) * textureSize(tex) * sample_count);\n" + "\n" + " const ivec2 resolve_coords = icoords * ivec2(bvec2(texture_parameters[index].scale_bias.xy));\n" + " const ivec2 aa_coords = resolve_coords / sample_count;\n" + " const ivec2 sample_loc = ivec2(resolve_coords % sample_count);\n" + " const int sample_index = sample_loc.x + (sample_loc.y * sample_count.y);\n" + " return texelFetch(tex, aa_coords, sample_index);\n" + "}\n\n"; + }; + + insert_msaa_sample_code("sampler2DMS"); + if (props.require_depth_conversion) + { + insert_msaa_sample_code("usampler2DMS"); + } + } } if (props.require_wpos) @@ -984,6 +1021,22 @@ namespace glsl return "TEX3D_Z24X8_RGBA8($_i, $0.xyz)"; case FUNCTION::TEXTURE_SAMPLE3D_DEPTH_RGBA_PROJ: return "TEX3D_Z24X8_RGBA8($_i, ($0.xyz / $0.w))"; + case FUNCTION::TEXTURE_SAMPLE2DMS: + case FUNCTION::TEXTURE_SAMPLE2DMS_BIAS: + return "TEX2D_MS($_i, $0.xy)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_PROJ: + return "TEX2D_MS($_i, $0.xy / $0.w)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_LOD: + case FUNCTION::TEXTURE_SAMPLE2DMS_GRAD: + return "TEX2D_MS($_i, $0.xy)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_SHADOW: + return "TEX2D_SHADOW_MS($_i, $0.xyz)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_SHADOW_PROJ: + return "TEX2D_SHADOWPROJ_MS($_i, $0)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_DEPTH_RGBA: + return "TEX2D_Z24X8_RGBA8_MS($_i, $0.xy)"; + case FUNCTION::TEXTURE_SAMPLE2DMS_DEPTH_RGBA_PROJ: + return "TEX2D_Z24X8_RGBA8_MS($_i, ($0.xy / $0.w))"; case FUNCTION::DFDX: return "dFdx($0)"; case FUNCTION::DFDY: @@ -995,6 +1048,8 @@ namespace glsl case FUNCTION::VERTEX_TEXTURE_FETCH3D: case FUNCTION::VERTEX_TEXTURE_FETCHCUBE: return "textureLod($t, $0.xyz, 0)"; + case FUNCTION::VERTEX_TEXTURE_FETCH2DMS: + return "texelFetch($t, ivec2($0.xy * textureSize($t)), 0)"; } rsx_log.error("Unexpected function request: %d", static_cast(f)); diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index 292912b063..5d704de3af 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -20,6 +20,11 @@ namespace rsx EXPAND_G, EXPAND_B, DEPTH_FLOAT, + DEPTH_COMPARE_OP, + DEPTH_COMPARE_1, + DEPTH_COMPARE_2, + FILTERED, + UNNORMALIZED_COORDS, GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A), EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A), diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 6e1461aafa..8805a62191 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -27,6 +27,7 @@ namespace glsl bool require_depth_conversion : 1; bool require_texture_ops : 1; bool require_shadow_ops : 1; + bool require_msaa_ops : 1; bool require_texture_expand : 1; bool require_srgb_to_linear : 1; bool require_linear_to_srgb : 1; diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index a0041cf63a..642415570f 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -307,6 +307,7 @@ usz vertex_program_storage_hash::operator()(const RSXVertexProgram &program) con usz hash = vertex_program_utils::get_vertex_program_ucode_hash(program); hash ^= program.output_mask; hash ^= program.texture_state.texture_dimensions; + hash ^= program.texture_state.multisampled_textures; return hash; } @@ -507,6 +508,7 @@ usz fragment_program_storage_hash::operator()(const RSXFragmentProgram& program) hash ^= program.texture_state.texture_dimensions; hash ^= program.texture_state.shadow_textures; hash ^= program.texture_state.redirected_textures; + hash ^= program.texture_state.multisampled_textures; hash ^= program.texcoord_control_mask; return hash; diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index b860a5d681..c0de8bf023 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -308,6 +308,8 @@ struct RSXFragmentProgram bool valid = false; + RSXFragmentProgram() = default; + rsx::texture_dimension_extended get_texture_dimension(u8 id) const { return rsx::texture_dimension_extended{static_cast((texture_state.texture_dimensions >> (id * 2)) & 0x3)}; @@ -324,10 +326,6 @@ struct RSXFragmentProgram return !!(texcoord_control_mask & (1u << index)); } - RSXFragmentProgram() - { - } - static RSXFragmentProgram clone(const RSXFragmentProgram& prog) { auto result = prog; diff --git a/rpcs3/Emu/RSX/Program/ShaderParam.h b/rpcs3/Emu/RSX/Program/ShaderParam.h index 4be8e15ca3..1b870037bf 100644 --- a/rpcs3/Emu/RSX/Program/ShaderParam.h +++ b/rpcs3/Emu/RSX/Program/ShaderParam.h @@ -64,10 +64,21 @@ enum class FUNCTION TEXTURE_SAMPLE3D_DEPTH_RGBA, TEXTURE_SAMPLE3D_DEPTH_RGBA_PROJ, + TEXTURE_SAMPLE2DMS, + TEXTURE_SAMPLE2DMS_BIAS, + TEXTURE_SAMPLE2DMS_PROJ, + TEXTURE_SAMPLE2DMS_LOD, + TEXTURE_SAMPLE2DMS_GRAD, + TEXTURE_SAMPLE2DMS_SHADOW, + TEXTURE_SAMPLE2DMS_SHADOW_PROJ, + TEXTURE_SAMPLE2DMS_DEPTH_RGBA, + TEXTURE_SAMPLE2DMS_DEPTH_RGBA_PROJ, + VERTEX_TEXTURE_FETCH1D, VERTEX_TEXTURE_FETCH2D, VERTEX_TEXTURE_FETCH3D, VERTEX_TEXTURE_FETCHCUBE, + VERTEX_TEXTURE_FETCH2DMS, // Meta TEXTURE_SAMPLE_MAX_BASE_ENUM = TEXTURE_SAMPLE_DEPTH_RGBA_PROJ_BASE, // Update if more base enums are added diff --git a/rpcs3/Emu/RSX/Program/VertexProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/VertexProgramDecompiler.cpp index 3a25240f70..6085f48137 100644 --- a/rpcs3/Emu/RSX/Program/VertexProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/VertexProgramDecompiler.cpp @@ -593,13 +593,15 @@ std::string VertexProgramDecompiler::Decompile() case RSX_VEC_OPCODE_TXL: { GetTex(); + const bool is_multisampled = m_prog.texture_state.multisampled_textures & (1 << d2.tex_num); + switch (m_prog.get_texture_dimension(d2.tex_num)) { case rsx::texture_dimension_extended::texture_dimension_1d: - SetDSTVec(getFunction(FUNCTION::VERTEX_TEXTURE_FETCH1D)); + SetDSTVec(is_multisampled ? getFunction(FUNCTION::VERTEX_TEXTURE_FETCH2DMS) : getFunction(FUNCTION::VERTEX_TEXTURE_FETCH1D)); break; case rsx::texture_dimension_extended::texture_dimension_2d: - SetDSTVec(getFunction(FUNCTION::VERTEX_TEXTURE_FETCH2D)); + SetDSTVec(getFunction(is_multisampled ? FUNCTION::VERTEX_TEXTURE_FETCH2DMS : FUNCTION::VERTEX_TEXTURE_FETCH2D)); break; case rsx::texture_dimension_extended::texture_dimension_3d: SetDSTVec(getFunction(FUNCTION::VERTEX_TEXTURE_FETCH3D)); diff --git a/rpcs3/Emu/RSX/Program/program_util.cpp b/rpcs3/Emu/RSX/Program/program_util.cpp index f838726d3e..2c00369b15 100644 --- a/rpcs3/Emu/RSX/Program/program_util.cpp +++ b/rpcs3/Emu/RSX/Program/program_util.cpp @@ -52,18 +52,20 @@ namespace rsx masked_transfer(slots_, src, mask); } - void fragment_program_texture_state::clear(u32 index) - { - const u16 clear_mask = ~(static_cast(1 << index)); - redirected_textures &= clear_mask; - shadow_textures &= clear_mask; - } - - void fragment_program_texture_state::import(const fragment_program_texture_state& other, u16 mask) - { - redirected_textures = other.redirected_textures & mask; - shadow_textures = other.shadow_textures & mask; - texture_dimensions = other.texture_dimensions & duplicate_and_extend(mask); + void fragment_program_texture_state::clear(u32 index) + { + const u16 clear_mask = ~(static_cast(1 << index)); + redirected_textures &= clear_mask; + shadow_textures &= clear_mask; + multisampled_textures &= clear_mask; + } + + void fragment_program_texture_state::import(const fragment_program_texture_state& other, u16 mask) + { + redirected_textures = other.redirected_textures & mask; + shadow_textures = other.shadow_textures & mask; + multisampled_textures = other.multisampled_textures & mask; + texture_dimensions = other.texture_dimensions & duplicate_and_extend(mask); } void fragment_program_texture_state::set_dimension(texture_dimension_extended type, u32 index) @@ -75,19 +77,22 @@ namespace rsx } bool fragment_program_texture_state::operator == (const fragment_program_texture_state& other) const - { - return texture_dimensions == other.texture_dimensions && - redirected_textures == other.redirected_textures && - shadow_textures == other.shadow_textures; - } - - void vertex_program_texture_state::clear(u32 /*index*/) { - // Nothing to do yet + return texture_dimensions == other.texture_dimensions && + redirected_textures == other.redirected_textures && + shadow_textures == other.shadow_textures && + multisampled_textures == other.multisampled_textures; + } + + void vertex_program_texture_state::clear(u32 index) + { + const u16 clear_mask = ~(static_cast(1 << index)); + multisampled_textures &= clear_mask; } void vertex_program_texture_state::import(const vertex_program_texture_state& other, u16 mask) { + multisampled_textures = other.multisampled_textures & mask; texture_dimensions = other.texture_dimensions & duplicate_and_extend(mask); } @@ -101,6 +106,7 @@ namespace rsx bool vertex_program_texture_state::operator == (const vertex_program_texture_state& other) const { - return texture_dimensions == other.texture_dimensions; + return texture_dimensions == other.texture_dimensions && + multisampled_textures == other.multisampled_textures; } } diff --git a/rpcs3/Emu/RSX/Program/program_util.h b/rpcs3/Emu/RSX/Program/program_util.h index 9868df4006..d6fd991101 100644 --- a/rpcs3/Emu/RSX/Program/program_util.h +++ b/rpcs3/Emu/RSX/Program/program_util.h @@ -33,14 +33,15 @@ namespace rsx }; #pragma pack(pop) - struct fragment_program_texture_state - { - u32 texture_dimensions = 0; - u16 redirected_textures = 0; - u16 shadow_textures = 0; - - void clear(u32 index); - void import(const fragment_program_texture_state& other, u16 mask); + struct fragment_program_texture_state + { + u32 texture_dimensions = 0; + u16 redirected_textures = 0; + u16 shadow_textures = 0; + u16 multisampled_textures = 0; + + void clear(u32 index); + void import(const fragment_program_texture_state& other, u16 mask); void set_dimension(texture_dimension_extended type, u32 index); bool operator == (const fragment_program_texture_state& other) const; }; @@ -48,6 +49,7 @@ namespace rsx struct vertex_program_texture_state { u32 texture_dimensions = 0; + u16 multisampled_textures = 0; void clear(u32 index); void import(const vertex_program_texture_state& other, u16 mask); diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 34047b3037..0fe5a691c1 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1738,6 +1738,12 @@ namespace rsx { current_vp_texture_state.clear(i); current_vp_texture_state.set_dimension(sampler_descriptors[i]->image_type, i); + + if (backend_config.supports_hw_msaa && + sampler_descriptors[i]->samples > 1) + { + current_vp_texture_state.multisampled_textures |= (1 << i); + } } } @@ -1978,6 +1984,15 @@ namespace rsx } } + if (backend_config.supports_hw_msaa && + sampler_descriptors[i]->samples > 1) + { + current_fp_texture_state.multisampled_textures |= (1 << i); + texture_control |= (static_cast(tex.zfunc()) << texture_control_bits::DEPTH_COMPARE_OP); + texture_control |= (static_cast(tex.mag_filter() != rsx::texture_magnify_filter::nearest) << texture_control_bits::FILTERED); + texture_control |= (((tex.format() & CELL_GCM_TEXTURE_UN) >> 6) << texture_control_bits::UNNORMALIZED_COORDS); + } + if (sampler_descriptors[i]->format_class != RSX_FORMAT_CLASS_COLOR) { switch (sampler_descriptors[i]->format_class) diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 5924a4bbdc..2e72650bea 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -588,6 +588,7 @@ namespace rsx bool supports_multidraw; // Draw call batching bool supports_hw_a2c; // Alpha to coverage bool supports_hw_renormalization; // Should be true on NV hardware which matches PS3 texture renormalization behaviour + bool supports_hw_msaa; // MSAA support bool supports_hw_a2one; // Alpha to one bool supports_hw_conditional_render; // Conditional render bool supports_passthrough_dma; // DMA passthrough diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index e3ea50afd9..ee4f758747 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -29,14 +29,25 @@ std::string VKFragmentDecompilerThread::compareFunction(COMPARE f, const std::st void VKFragmentDecompilerThread::insertHeader(std::stringstream & OS) { + int version = 420; + std::vector required_extensions; + if (device_props.has_native_half_support) { - OS << "#version 450\n"; - OS << "#extension GL_EXT_shader_explicit_arithmetic_types_float16: enable\n"; + version = std::max(version, 450); + required_extensions.emplace_back("GL_EXT_shader_explicit_arithmetic_types_float16"); } - else + + if (properties.multisampled_sampler_mask) { - OS << "#version 420\n"; + version = std::max(version, 450); + required_extensions.emplace_back("GL_ARB_shader_texture_image_samples"); + } + + OS << "#version " << version << "\n"; + for (const auto ext : required_extensions) + { + OS << "#extension " << ext << ": require\n"; } OS << "#extension GL_ARB_separate_shader_objects: enable\n\n"; @@ -128,11 +139,21 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) for (const ParamItem& PI : PT.items) { std::string samplerType = PT.type; - int index = atoi(&PI.name[3]); + ensure(PI.name.length() > 3); + int index = atoi(&PI.name[3]); const auto mask = (1 << index); - if (properties.shadow_sampler_mask & mask) + if (properties.multisampled_sampler_mask & mask) + { + if (samplerType != "sampler1D" && samplerType != "sampler2D") + { + rsx_log.error("Unexpected multisampled image type '%s'", samplerType); + } + + samplerType = "sampler2DMS"; + } + else if (properties.shadow_sampler_mask & mask) { if (properties.common_access_sampler_mask & mask) { @@ -162,7 +183,7 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) inputs.push_back(in); - OS << "layout(set=0, binding=" << location++ << ") uniform u" << PT.type << " " << in.name << ";\n"; + OS << "layout(set=0, binding=" << location++ << ") uniform u" << samplerType << " " << in.name << ";\n"; } } } @@ -243,6 +264,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_wpos = !!(properties.in_register_mask & in_wpos); m_shader_props.require_texture_ops = properties.has_tex_op; m_shader_props.require_shadow_ops = properties.shadow_sampler_mask != 0; + m_shader_props.require_msaa_ops = m_prog.texture_state.multisampled_textures != 0; m_shader_props.require_texture_expand = properties.has_exp_tex_op; m_shader_props.require_srgb_to_linear = properties.has_upg; m_shader_props.require_linear_to_srgb = properties.has_pkg; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 2b9d5de8cf..8c4d30f695 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -506,7 +506,7 @@ VKGSRender::VKGSRender() : GSRender() else m_vertex_cache = std::make_unique(); - m_shaders_cache = std::make_unique(*m_prog_buffer, "vulkan", "v1.92"); + m_shaders_cache = std::make_unique(*m_prog_buffer, "vulkan", "v1.93"); for (u32 i = 0; i < m_swapchain->get_swap_image_count(); ++i) { @@ -539,7 +539,8 @@ VKGSRender::VKGSRender() : GSRender() // This is here for visual consistency - will be removed when AA problems due to mipmaps are fixed if (g_cfg.video.antialiasing_level != msaa_level::none) { - backend_config.supports_hw_a2c = VK_TRUE; + backend_config.supports_hw_msaa = true; + backend_config.supports_hw_a2c = true; backend_config.supports_hw_a2one = m_device->get_alpha_to_one_support(); } @@ -2233,7 +2234,7 @@ void VKGSRender::close_and_submit_command_buffer(vk::fence* pFence, VkSemaphore primary_submit_info.wait_on(wait_semaphore, pipeline_stage_flags); } - if (const auto wait_sema = std::exchange(m_dangling_semaphore_signal, VK_NULL_HANDLE)) + if (const auto wait_sema = std::exchange(m_dangling_semaphore_signal, nullptr)) { // TODO: Sync on VS stage primary_submit_info.wait_on(wait_sema, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index dc9797bcf9..9023c93924 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -278,7 +278,7 @@ vk::viewable_image* VKGSRender::get_present_source(vk::present_surface_info* inf // Check the surface store first const auto format_bpp = rsx::get_format_block_size_in_bytes(info->format); const auto overlap_info = m_rtts.get_merged_texture_memory_region(*m_current_command_buffer, - info->address, info->width, info->height, info->pitch, format_bpp, rsx::surface_access::shader_read); + info->address, info->width, info->height, info->pitch, format_bpp, rsx::surface_access::transfer_read); if (!overlap_info.empty()) { @@ -311,8 +311,7 @@ vk::viewable_image* VKGSRender::get_present_source(vk::present_surface_info* inf if (viable) { - surface->read_barrier(*m_current_command_buffer); - image_to_flip = section.surface->get_surface(rsx::surface_access::shader_read); + image_to_flip = section.surface->get_surface(rsx::surface_access::transfer_read); std::tie(info->width, info->height) = rsx::apply_resolution_scale( std::min(surface_width, info->width), diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp b/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp index d91d204b8b..0f23f5c901 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp @@ -337,8 +337,6 @@ namespace vk if (!is_depth_surface()) [[likely]] { - ensure(current_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - // This is the source; finish writing before reading vk::insert_image_memory_barrier( cmd, this->value, @@ -767,17 +765,11 @@ namespace vk { last_rw_access_tag = rsx::get_shared_tag(); - if (samples() == 1 || access_type == rsx::surface_access::shader_write) + if (samples() == 1 || !access_type.is_transfer()) { return this; } - if (access_type == rsx::surface_access::gpu_reference) - { - // WARNING: Can return MSAA data result if no read barrier was issued - return resolve_surface ? resolve_surface.get() : this; - } - // A read barrier should have been called before this! ensure(resolve_surface); // "Read access without explicit barrier" ensure(!(msaa_flags & rsx::surface_state_flags::require_resolve)); @@ -803,19 +795,10 @@ namespace vk void render_target::texture_barrier(vk::command_buffer& cmd) { - if (samples() == 1) - { - if (!write_barrier_sync_tag) write_barrier_sync_tag++; // Activate barrier sync - cyclic_reference_sync_tag = write_barrier_sync_tag; // Match tags + if (!write_barrier_sync_tag) write_barrier_sync_tag++; // Activate barrier sync + cyclic_reference_sync_tag = write_barrier_sync_tag; // Match tags - vk::insert_texture_barrier(cmd, this, VK_IMAGE_LAYOUT_GENERAL); - return; - } - - if (msaa_flags & rsx::surface_state_flags::require_resolve) - { - resolve(cmd); - } + vk::insert_texture_barrier(cmd, this, VK_IMAGE_LAYOUT_GENERAL); } void render_target::reset_surface_counters() @@ -832,14 +815,7 @@ namespace vk return vk::viewable_image::get_view(VK_REMAP_IDENTITY, remap, mask); } - if (!resolve_surface) [[likely]] - { - return vk::viewable_image::get_view(remap_encoding, remap, mask); - } - else - { - return resolve_surface->get_view(remap_encoding, remap, mask); - } + return vk::viewable_image::get_view(remap_encoding, remap, mask); } void render_target::memory_barrier(vk::command_buffer& cmd, rsx::surface_access access) @@ -933,7 +909,7 @@ namespace vk if (msaa_flags & rsx::surface_state_flags::require_resolve) { - if (access.is_transfer_or_read()) + if (access.is_transfer() && access.is_read()) { // Only do this step when read access is required get_resolve_target_safe(cmd); @@ -967,7 +943,7 @@ namespace vk { auto& section = old_contents[i]; auto src_texture = static_cast(section.source); - src_texture->read_barrier(cmd); + src_texture->memory_barrier(cmd, rsx::surface_access::transfer_read); if (!accept_all && !src_texture->test()) [[likely]] { diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.h b/rpcs3/Emu/RSX/VK/VKRenderTargets.h index bd967bcd9b..d2f85945cc 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.h +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.h @@ -91,7 +91,7 @@ namespace vk static std::pair get_attachment_create_flags(VkFormat format, u8 samples) { - if (g_cfg.video.strict_rendering_mode || samples > 1) + if (g_cfg.video.strict_rendering_mode) { return {}; } @@ -152,11 +152,11 @@ namespace vk } auto [usage_flags, create_flags] = get_attachment_create_flags(requested_format, samples); - usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (samples == 1) [[likely]] { - usage_flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + usage_flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } else { diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.h b/rpcs3/Emu/RSX/VK/VKTextureCache.h index bd0ea637fa..3026579334 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.h +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.h @@ -197,8 +197,8 @@ namespace vk if (context == rsx::texture_upload_context::framebuffer_storage) { auto surface = vk::as_rtt(vram_texture); - surface->read_barrier(cmd); - locked_resource = surface->get_surface(rsx::surface_access::shader_read); + surface->memory_barrier(cmd, rsx::surface_access::transfer_read); + locked_resource = surface->get_surface(rsx::surface_access::transfer_read); transfer_width *= surface->samples_x; transfer_height *= surface->samples_y; } diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp index 0d7576a34a..b49655ad31 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp @@ -134,7 +134,25 @@ void VKVertexDecompilerThread::insertConstants(std::stringstream & OS, const std inputs.push_back(in); - OS << "layout(set = 0, binding=" << location++ << ") uniform " << PT.type << " " << PI.name << ";\n"; + auto samplerType = PT.type; + + if (m_prog.texture_state.multisampled_textures) [[ unlikely ]] + { + ensure(PI.name.length() > 3); + int index = atoi(&PI.name[3]); + + if (m_prog.texture_state.multisampled_textures & (1 << index)) + { + if (samplerType != "sampler1D" && samplerType != "sampler2D") + { + rsx_log.error("Unexpected multisampled sampler type '%s'", samplerType); + } + + samplerType = "sampler2DMS"; + } + } + + OS << "layout(set = 0, binding=" << location++ << ") uniform " << samplerType << " " << PI.name << ";\n"; } } } diff --git a/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp b/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp index d5c66b7d89..bdcf73bad0 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp @@ -113,12 +113,6 @@ namespace vk void insert_texture_barrier(VkCommandBuffer cmd, vk::image* image, VkImageLayout new_layout) { - if (image->samples() > 1) - { - // This barrier is pointless for multisampled images as they require a resolve operation before access anyway - return; - } - insert_texture_barrier(cmd, image->value, image->current_layout, new_layout, { image->aspect(), 0, 1, 0, 1 }); image->current_layout = new_layout; } diff --git a/rpcs3/Emu/RSX/rsx_cache.h b/rpcs3/Emu/RSX/rsx_cache.h index 15b3066742..e074b31dbf 100644 --- a/rpcs3/Emu/RSX/rsx_cache.h +++ b/rpcs3/Emu/RSX/rsx_cache.h @@ -36,6 +36,10 @@ namespace rsx u32 vp_entry; u16 vp_jump_table[32]; + u16 vp_multisampled_textures; + u16 vp_reserved_0; + u32 vp_reserved_1; + u32 fp_ctrl; u32 fp_texture_dimensions; u32 fp_texcoord_control; @@ -44,8 +48,8 @@ namespace rsx u16 fp_lighting_flags; u16 fp_shadow_textures; u16 fp_redirected_textures; - u16 unused_0; // Retained for binary compatibility - u64 unused_1; // Retained for binary compatibility + u16 fp_multisampled_textures; + u64 fp_reserved_0; pipeline_storage_type pipeline_properties; }; @@ -311,6 +315,8 @@ namespace rsx state_hash ^= rpcs3::hash_base(data.fp_lighting_flags); state_hash ^= rpcs3::hash_base(data.fp_shadow_textures); state_hash ^= rpcs3::hash_base(data.fp_redirected_textures); + state_hash ^= rpcs3::hash_base(data.vp_multisampled_textures); + state_hash ^= rpcs3::hash_base(data.fp_multisampled_textures); const std::string pipeline_file_name = fmt::format("%llX+%llX+%llX+%llX.bin", data.vertex_program_hash, data.fragment_program_hash, data.pipeline_storage_hash, state_hash); const std::string pipeline_path = root_path + "/pipelines/" + pipeline_class_name + "/" + version_prefix + "/" + pipeline_file_name; @@ -358,6 +364,7 @@ namespace rsx vp.output_mask = data.vp_ctrl; vp.texture_state.texture_dimensions = data.vp_texture_dimensions; + vp.texture_state.multisampled_textures = data.vp_multisampled_textures; vp.base_address = data.vp_base_address; vp.entry = data.vp_entry; @@ -379,6 +386,7 @@ namespace rsx fp.texture_state.texture_dimensions = data.fp_texture_dimensions; fp.texture_state.shadow_textures = data.fp_shadow_textures; fp.texture_state.redirected_textures = data.fp_redirected_textures; + fp.texture_state.multisampled_textures = data.fp_multisampled_textures; fp.texcoord_control_mask = data.fp_texcoord_control; fp.two_sided_lighting = !!(data.fp_lighting_flags & 0x1); @@ -395,6 +403,7 @@ namespace rsx data_block.vp_ctrl = vp.output_mask; data_block.vp_texture_dimensions = vp.texture_state.texture_dimensions; + data_block.vp_multisampled_textures = vp.texture_state.multisampled_textures; data_block.vp_base_address = vp.base_address; data_block.vp_entry = vp.entry; @@ -424,6 +433,7 @@ namespace rsx data_block.fp_lighting_flags = u16(fp.two_sided_lighting); data_block.fp_shadow_textures = fp.texture_state.shadow_textures; data_block.fp_redirected_textures = fp.texture_state.redirected_textures; + data_block.fp_multisampled_textures = fp.texture_state.multisampled_textures; return data_block; }