diff --git a/rpcs3/Emu/RSX/Common/GLSLCommon.h b/rpcs3/Emu/RSX/Common/GLSLCommon.h index 9431db1fdb..839f90be7f 100644 --- a/rpcs3/Emu/RSX/Common/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Common/GLSLCommon.h @@ -430,6 +430,23 @@ namespace glsl "}\n\n"; } + static void insert_rop_init(std::ostream& OS) + { + OS << + " if ((rop_control & (1u << 9)) != 0)\n" + " {\n" + " // Convert x,y to linear address\n" + " uvec2 stipple_coord = uvec2(gl_FragCoord.xy) % uvec2(32u, 32u);\n" + " uint address = stipple_coord.y * 32u + stipple_coord.x;\n" + " uint mask = (1u << (address & 31u));\n\n" + + " if ((stipple_pattern[address >> 7u][(address >> 5u) & 3u] & mask) == 0u)\n" + " {\n" + " _kill();\n" + " }\n" + " }\n\n"; + } + static void insert_rop(std::ostream& OS, const shader_properties& props) { const std::string reg0 = props.fp32_outputs ? "r0" : "h0"; diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index 981fbabcfd..0acd64e202 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -190,6 +190,11 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS) OS << "{\n"; OS << " sampler_info texture_parameters[16];\n"; OS << "};\n\n"; + + OS << "layout(std140, binding = " << GL_RASTERIZER_STATE_BIND_SLOT << ") uniform RasterizerHeap\n"; + OS << "{\n"; + OS << " uvec4 stipple_pattern[8];\n"; + OS << "};\n\n"; } void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) @@ -303,6 +308,8 @@ void GLFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "void main()\n"; OS << "{\n"; + ::glsl::insert_rop_init(OS); + OS << "\n" << " fs_main();\n\n"; glsl::insert_rop(OS, m_shader_props); diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index cd26edbff4..1c0b55f23a 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -200,6 +200,7 @@ void GLGSRender::on_init_thread() m_index_ring_buffer = std::make_unique(); m_vertex_instructions_buffer = std::make_unique(); m_fragment_instructions_buffer = std::make_unique(); + m_raster_env_ring_buffer = std::make_unique(); } else { @@ -213,6 +214,7 @@ void GLGSRender::on_init_thread() m_index_ring_buffer = std::make_unique(); m_vertex_instructions_buffer = std::make_unique(); m_fragment_instructions_buffer = std::make_unique(); + m_raster_env_ring_buffer = std::make_unique(); } m_attrib_ring_buffer->create(gl::buffer::target::texture, 256 * 0x100000); @@ -223,6 +225,7 @@ void GLGSRender::on_init_thread() m_vertex_env_buffer->create(gl::buffer::target::uniform, 16 * 0x100000); m_texture_parameters_buffer->create(gl::buffer::target::uniform, 16 * 0x100000); m_vertex_layout_buffer->create(gl::buffer::target::uniform, 16 * 0x100000); + m_raster_env_ring_buffer->create(gl::buffer::target::uniform, 16 * 0x100000); if (shadermode == shader_mode::async_with_interpreter || shadermode == shader_mode::interpreter_only) { @@ -449,6 +452,11 @@ void GLGSRender::on_exit() m_fragment_instructions_buffer->remove(); } + if (m_raster_env_ring_buffer) + { + m_raster_env_ring_buffer->remove(); + } + m_null_textures.clear(); m_text_printer.close(); m_gl_texture_cache.destroy(); @@ -704,6 +712,7 @@ void GLGSRender::load_program_env() const bool update_fragment_env = !!(m_graphics_state & rsx::pipeline_state::fragment_state_dirty); const bool update_fragment_texture_env = !!(m_graphics_state & rsx::pipeline_state::fragment_texture_state_dirty); const bool update_instruction_buffers = (!!m_interpreter_state && m_shader_interpreter.is_interpreter(m_program)); + const bool update_raster_env = (rsx::method_registers.polygon_stipple_enabled() && !!(m_graphics_state & rsx::pipeline_state::polygon_stipple_pattern_dirty)); m_program->use(); @@ -714,6 +723,7 @@ void GLGSRender::load_program_env() if (update_fragment_texture_env) m_texture_parameters_buffer->reserve_storage_on_heap(256); if (update_fragment_constants) m_fragment_constants_buffer->reserve_storage_on_heap(align(fragment_constants_size, 256)); if (update_transform_constants) m_transform_constants_buffer->reserve_storage_on_heap(8192); + if (update_raster_env) m_raster_env_ring_buffer->reserve_storage_on_heap(128); if (update_instruction_buffers) { @@ -779,6 +789,16 @@ void GLGSRender::load_program_env() m_texture_parameters_buffer->bind_range(GL_FRAGMENT_TEXTURE_PARAMS_BIND_SLOT, mapping.second, 256); } + if (update_raster_env) + { + auto mapping = m_raster_env_ring_buffer->alloc_from_heap(128, m_uniform_buffer_offset_align); + + std::memcpy(mapping.first, rsx::method_registers.polygon_stipple_pattern(), 128); + m_raster_env_ring_buffer->bind_range(GL_RASTERIZER_STATE_BIND_SLOT, mapping.second, 128); + + m_graphics_state &= ~(rsx::pipeline_state::polygon_stipple_pattern_dirty); + } + if (update_instruction_buffers) { if (m_interpreter_state & rsx::vertex_program_dirty) @@ -830,6 +850,7 @@ void GLGSRender::load_program_env() if (update_fragment_texture_env) m_texture_parameters_buffer->unmap(); if (update_fragment_constants) m_fragment_constants_buffer->unmap(); if (update_transform_constants) m_transform_constants_buffer->unmap(); + if (update_raster_env) m_raster_env_ring_buffer->unmap(); if (update_instruction_buffers) { diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 9f09224b3c..fbf36fe6fe 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -99,6 +99,7 @@ private: std::unique_ptr m_index_ring_buffer; std::unique_ptr m_vertex_instructions_buffer; std::unique_ptr m_fragment_instructions_buffer; + std::unique_ptr m_raster_env_ring_buffer; // Identity buffer used to fix broken gl_VertexID on ATI stack std::unique_ptr m_identity_index_buffer; diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.h b/rpcs3/Emu/RSX/GL/GLHelpers.h index 6e896115d5..7246b1edaf 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.h +++ b/rpcs3/Emu/RSX/GL/GLHelpers.h @@ -28,9 +28,10 @@ #define GL_FRAGMENT_CONSTANT_BUFFERS_BIND_SLOT 3 #define GL_FRAGMENT_STATE_BIND_SLOT 4 #define GL_FRAGMENT_TEXTURE_PARAMS_BIND_SLOT 5 -#define GL_INTERPRETER_VERTEX_BLOCK 6 -#define GL_INTERPRETER_FRAGMENT_BLOCK 7 -#define GL_COMPUTE_BUFFER_SLOT(index) (index + 8) +#define GL_RASTERIZER_STATE_BIND_SLOT 6 +#define GL_INTERPRETER_VERTEX_BLOCK 7 +#define GL_INTERPRETER_FRAGMENT_BLOCK 8 +#define GL_COMPUTE_BUFFER_SLOT(index) (index + 9) // Noop keyword outside of Windows (used in log_debug) #if !defined(_WIN32) && !defined(APIENTRY) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index c1b1df4989..bab4b951bf 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -792,6 +792,11 @@ namespace rsx } } + if (rsx::method_registers.polygon_stipple_enabled()) + { + rop_control |= (1u << 9); + } + // Generate wpos coefficients // wpos equation is now as follows: // wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0) diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 771440d128..590abd46d9 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -82,6 +82,9 @@ namespace rsx scissor_setup_invalid = 0x400, // Scissor configuration is broken scissor_setup_clipped = 0x800, // Scissor region is cropped by viewport constraint + polygon_stipple_pattern_dirty = 0x1000, // Rasterizer stippling pattern changed + line_stipple_pattern_dirty = 0x2000, // Line stippling pattern changed + invalidate_pipeline_bits = fragment_program_dirty | vertex_program_dirty, memory_barrier_bits = framebuffer_reads_dirty, all_dirty = ~0u diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index 1239a9c6dc..3570a4955e 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -206,6 +206,11 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) OS << " sampler_info texture_parameters[16];\n"; OS << "};\n\n"; + OS << "layout(std140, set = 0, binding = " << std::to_string(m_binding_table.rasterizer_env_bind_slot) << ") uniform RasterizerHeap\n"; + OS << "{\n"; + OS << " uvec4 stipple_pattern[8];\n"; + OS << "};\n\n"; + vk::glsl::program_input in; in.location = m_binding_table.fragment_constant_buffers_bind_slot; in.domain = glsl::glsl_fragment_program; @@ -220,6 +225,10 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) in.location = m_binding_table.fragment_texture_params_bind_slot; in.name = "TextureParametersBuffer"; inputs.push_back(in); + + in.location = m_binding_table.rasterizer_env_bind_slot; + in.name = "RasterizerHeap"; + inputs.push_back(in); } void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) @@ -333,6 +342,8 @@ void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "void main()\n"; OS << "{\n"; + ::glsl::insert_rop_init(OS); + OS << "\n" << " fs_main();\n\n"; glsl::insert_rop(OS, m_shader_props); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 01229dcc0f..547b1e5831 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -279,6 +279,13 @@ namespace idx++; + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding_table.rasterizer_env_bind_slot; + + idx++; + for (auto binding = binding_table.textures_first_bind_slot; binding < binding_table.vertex_textures_first_bind_slot; binding++) @@ -464,6 +471,7 @@ VKGSRender::VKGSRender() : GSRender() m_transform_constants_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_TRANSFORM_CONSTANTS_BUFFER_SIZE_M * 0x100000, "transform constants buffer"); m_index_buffer_ring_info.create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_INDEX_RING_BUFFER_SIZE_M * 0x100000, "index buffer"); m_texture_upload_buffer_ring_info.create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_TEXTURE_UPLOAD_RING_BUFFER_SIZE_M * 0x100000, "texture upload buffer", 32 * 0x100000); + m_raster_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, "raster env buffer"); const auto shadermode = g_cfg.video.shadermode.get(); @@ -473,6 +481,9 @@ VKGSRender::VKGSRender() : GSRender() m_fragment_instructions_buffer.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 64 * 0x100000, "fragment instructions buffer", 2048); } + // Initiailize optional allocation information with placeholders + m_raster_env_buffer_info = { m_raster_env_ring_info.heap->value, 0, 128 }; + const auto limits = m_device->gpu().get_limits(); m_texbuffer_view_size = std::min(limits.maxTexelBufferElements, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000u); @@ -606,6 +617,7 @@ VKGSRender::~VKGSRender() m_texture_upload_buffer_ring_info.destroy(); m_vertex_instructions_buffer.destroy(); m_fragment_instructions_buffer.destroy(); + m_raster_env_ring_info.destroy(); //Fallback bindables null_buffer.reset(); @@ -806,7 +818,8 @@ void VKGSRender::check_heap_status(u32 flags) m_vertex_layout_ring_info.is_critical() || m_fragment_constants_ring_info.is_critical() || m_transform_constants_ring_info.is_critical() || - m_index_buffer_ring_info.is_critical(); + m_index_buffer_ring_info.is_critical() || + m_raster_env_ring_info.is_critical(); } else if (flags) { @@ -829,7 +842,7 @@ void VKGSRender::check_heap_status(u32 flags) heap_critical = m_vertex_env_ring_info.is_critical(); break; case VK_HEAP_CHECK_FRAGMENT_ENV_STORAGE: - heap_critical = m_fragment_env_ring_info.is_critical(); + heap_critical = m_fragment_env_ring_info.is_critical() || m_raster_env_ring_info.is_critical(); break; case VK_HEAP_CHECK_TEXTURE_ENV_STORAGE: heap_critical = m_fragment_texture_params_ring_info.is_critical(); @@ -880,6 +893,7 @@ void VKGSRender::check_heap_status(u32 flags) m_transform_constants_ring_info.reset_allocation_stats(); m_attrib_ring_info.reset_allocation_stats(); m_texture_upload_buffer_ring_info.reset_allocation_stats(); + m_raster_env_ring_info.reset_allocation_stats(); m_current_frame->reset_heap_ptrs(); m_last_heap_sync_time = get_system_time(); } @@ -1652,6 +1666,7 @@ void VKGSRender::load_program_env() const bool update_fragment_env = !!(m_graphics_state & rsx::pipeline_state::fragment_state_dirty); const bool update_fragment_texture_env = !!(m_graphics_state & rsx::pipeline_state::fragment_texture_state_dirty); const bool update_instruction_buffers = (!!m_interpreter_state && m_shader_interpreter.is_interpreter(m_program)); + const bool update_raster_env = (rsx::method_registers.polygon_stipple_enabled() && !!(m_graphics_state & rsx::pipeline_state::polygon_stipple_pattern_dirty)); if (update_vertex_env) { @@ -1731,6 +1746,20 @@ void VKGSRender::load_program_env() m_fragment_texture_params_buffer_info = { m_fragment_texture_params_ring_info.heap->value, mem, 256 }; } + if (update_raster_env) + { + check_heap_status(VK_HEAP_CHECK_FRAGMENT_ENV_STORAGE); + + auto mem = m_raster_env_ring_info.alloc<256>(256); + auto buf = m_raster_env_ring_info.map(mem, 128); + + std::memcpy(buf, rsx::method_registers.polygon_stipple_pattern(), 128); + m_raster_env_ring_info.unmap(); + m_raster_env_buffer_info = { m_raster_env_ring_info.heap->value, mem, 128 }; + + m_graphics_state &= ~(rsx::pipeline_state::polygon_stipple_pattern_dirty); + } + if (update_instruction_buffers) { if (m_interpreter_state & rsx::vertex_program_dirty) @@ -1778,6 +1807,7 @@ void VKGSRender::load_program_env() m_program->bind_uniform(m_vertex_constants_buffer_info, binding_table.vertex_constant_buffers_bind_slot, m_current_frame->descriptor_set); m_program->bind_uniform(m_fragment_env_buffer_info, binding_table.fragment_state_bind_slot, m_current_frame->descriptor_set); m_program->bind_uniform(m_fragment_texture_params_buffer_info, binding_table.fragment_texture_params_bind_slot, m_current_frame->descriptor_set); + m_program->bind_uniform(m_raster_env_buffer_info, binding_table.rasterizer_env_bind_slot, m_current_frame->descriptor_set); if (!m_shader_interpreter.is_interpreter(m_program)) { @@ -1869,7 +1899,8 @@ void VKGSRender::close_and_submit_command_buffer(vk::fence* pFence, VkSemaphore m_fragment_constants_ring_info.dirty() || m_index_buffer_ring_info.dirty() || m_transform_constants_ring_info.dirty() || - m_texture_upload_buffer_ring_info.dirty()) + m_texture_upload_buffer_ring_info.dirty() || + m_raster_env_ring_info.dirty()) { std::lock_guard lock(m_secondary_cb_guard); m_secondary_command_buffer.begin(); @@ -1883,6 +1914,7 @@ void VKGSRender::close_and_submit_command_buffer(vk::fence* pFence, VkSemaphore m_index_buffer_ring_info.sync(m_secondary_command_buffer); m_transform_constants_ring_info.sync(m_secondary_command_buffer); m_texture_upload_buffer_ring_info.sync(m_secondary_command_buffer); + m_raster_env_ring_info.sync(m_secondary_command_buffer); m_secondary_command_buffer.end(); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 169caf3ff9..89fc9ce31f 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -208,6 +208,7 @@ namespace vk s64 frag_texparam_heap_ptr = 0; s64 index_heap_ptr = 0; s64 texture_upload_heap_ptr = 0; + s64 rasterizer_env_heap_ptr = 0; u64 last_frame_sync_time = 0; @@ -230,6 +231,7 @@ namespace vk vtx_const_heap_ptr = other.vtx_const_heap_ptr; index_heap_ptr = other.index_heap_ptr; texture_upload_heap_ptr = other.texture_upload_heap_ptr; + rasterizer_env_heap_ptr = other.rasterizer_env_heap_ptr; } //Exchange storage (non-copyable) @@ -238,7 +240,10 @@ namespace vk std::swap(buffer_views_to_clean, other.buffer_views_to_clean); } - void tag_frame_end(s64 attrib_loc, s64 vtxenv_loc, s64 fragenv_loc, s64 vtxlayout_loc, s64 fragtex_loc, s64 fragconst_loc,s64 vtxconst_loc, s64 index_loc, s64 texture_loc) + void tag_frame_end( + s64 attrib_loc, s64 vtxenv_loc, s64 fragenv_loc, s64 vtxlayout_loc, + s64 fragtex_loc, s64 fragconst_loc,s64 vtxconst_loc, s64 index_loc, + s64 texture_loc, s64 rasterizer_loc) { attrib_heap_ptr = attrib_loc; vtx_env_heap_ptr = vtxenv_loc; @@ -249,6 +254,7 @@ namespace vk vtx_const_heap_ptr = vtxconst_loc; index_heap_ptr = index_loc; texture_upload_heap_ptr = texture_loc; + rasterizer_env_heap_ptr = rasterizer_loc; last_frame_sync_time = get_system_time(); } @@ -425,6 +431,7 @@ private: vk::data_heap m_vertex_layout_ring_info; // Vertex layout structure vk::data_heap m_index_buffer_ring_info; // Index data vk::data_heap m_texture_upload_buffer_ring_info; // Texture upload heap + vk::data_heap m_raster_env_ring_info; // Raster control such as polygon and line stipple vk::data_heap m_fragment_instructions_buffer; vk::data_heap m_vertex_instructions_buffer; @@ -435,6 +442,7 @@ private: VkDescriptorBufferInfo m_vertex_constants_buffer_info; VkDescriptorBufferInfo m_fragment_constants_buffer_info; VkDescriptorBufferInfo m_fragment_texture_params_buffer_info; + VkDescriptorBufferInfo m_raster_env_buffer_info; VkDescriptorBufferInfo m_vertex_instructions_buffer_info; VkDescriptorBufferInfo m_fragment_instructions_buffer_info; diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index e049174bb7..f7f5ba01aa 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -245,8 +245,9 @@ namespace vk u8 fragment_texture_params_bind_slot = 4; u8 vertex_buffers_first_bind_slot = 5; u8 conditional_render_predicate_slot = 8; - u8 textures_first_bind_slot = 9; - u8 vertex_textures_first_bind_slot = 9; // Invalid, has to be initialized properly + u8 rasterizer_env_bind_slot = 9; + u8 textures_first_bind_slot = 10; + u8 vertex_textures_first_bind_slot = 10; // Invalid, has to be initialized properly u8 total_descriptor_bindings = vertex_textures_first_bind_slot; // Invalid, has to be initialized properly }; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 42f691fe2b..76b729ca3a 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -122,7 +122,8 @@ void VKGSRender::advance_queued_frames() m_fragment_constants_ring_info.get_current_put_pos_minus_one(), m_transform_constants_ring_info.get_current_put_pos_minus_one(), m_index_buffer_ring_info.get_current_put_pos_minus_one(), - m_texture_upload_buffer_ring_info.get_current_put_pos_minus_one()); + m_texture_upload_buffer_ring_info.get_current_put_pos_minus_one(), + m_raster_env_ring_info.get_current_put_pos_minus_one()); m_queued_frames.push_back(m_current_frame); verify(HERE), m_queued_frames.size() <= VK_MAX_ASYNC_FRAMES; diff --git a/rpcs3/Emu/RSX/rsx_decode.h b/rpcs3/Emu/RSX/rsx_decode.h index 04f1885fd2..4b2f81fb2e 100644 --- a/rpcs3/Emu/RSX/rsx_decode.h +++ b/rpcs3/Emu/RSX/rsx_decode.h @@ -4074,6 +4074,29 @@ struct registers_decoder } }; +template<> +struct registers_decoder +{ + struct decoded_type + { + private: + u32 value; + + public: + decoded_type(u32 value) : value(value) {} + + bool enabled() const + { + return value > 0; + } + }; + + static std::string dump(decoded_type&& decoded_values) + { + return std::string("polygon_stipple: ") + (decoded_values.enabled()? "true" : "false"); + } +}; + #define EXPAND_RANGE_1(index, MACRO) \ MACRO(index) diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index 7bc8b21ffd..2e9d3aed4d 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -3120,6 +3120,8 @@ namespace rsx bind_range(); bind(); bind(); + bind>(); + bind_array>(); //NV308A (0xa400..0xbffc!) bind_range(); diff --git a/rpcs3/Emu/RSX/rsx_methods.h b/rpcs3/Emu/RSX/rsx_methods.h index 0d3b39bda7..8750567c7f 100644 --- a/rpcs3/Emu/RSX/rsx_methods.h +++ b/rpcs3/Emu/RSX/rsx_methods.h @@ -1706,6 +1706,16 @@ namespace rsx { return decode().texcoord_mask(); } + + const void* polygon_stipple_pattern() const + { + return registers.data() + NV4097_SET_POLYGON_STIPPLE_PATTERN; + } + + bool polygon_stipple_enabled() const + { + return decode().enabled(); + } }; extern rsx_state method_registers;