#include "GLOverlays.h" #include "Emu/system_config.h" #include "../rsx_utils.h" #include "../Program/RSXOverlay.h" namespace gl { // Lame std::unordered_map> g_overlay_passes; void destroy_overlay_passes() { for (auto& [key, prog] : g_overlay_passes) { prog->destroy(); } g_overlay_passes.clear(); } void overlay_pass::create() { if (!compiled) { fs.create(::glsl::program_domain::glsl_fragment_program, fs_src); fs.compile(); vs.create(::glsl::program_domain::glsl_vertex_program, vs_src); vs.compile(); program_handle.create(); program_handle.attach(vs); program_handle.attach(fs); program_handle.link(); fbo.create(); m_sampler.create(); m_sampler.apply_defaults(static_cast(m_input_filter)); m_vertex_data_buffer.create(); int old_vao; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); m_vao.create(); m_vao.bind(); m_vao.array_buffer = m_vertex_data_buffer; auto ptr = buffer_pointer(&m_vao); m_vao[0] = ptr; glBindVertexArray(old_vao); compiled = true; } } void overlay_pass::destroy() { if (compiled) { program_handle.remove(); vs.remove(); fs.remove(); fbo.remove(); m_vao.remove(); m_vertex_data_buffer.remove(); m_sampler.remove(); compiled = false; } } void overlay_pass::emit_geometry() { int old_vao; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); m_vao.bind(); glDrawArrays(primitives, 0, num_drawable_elements); glBindVertexArray(old_vao); } void overlay_pass::run(gl::command_context& cmd, const areau& region, GLuint target_texture, GLuint image_aspect_bits, bool enable_blending) { if (!compiled) { rsx_log.error("You must initialize overlay passes with create() before calling run()"); return; } GLint viewport[4]; std::unique_ptr save_fbo; if (target_texture) { save_fbo = std::make_unique(fbo); switch (image_aspect_bits) { case gl::image_aspect::color: fbo.color[0] = target_texture; fbo.draw_buffer(fbo.color[0]); break; case gl::image_aspect::depth: fbo.draw_buffer(fbo.no_color); fbo.depth = target_texture; break; case gl::image_aspect::depth | gl::image_aspect::stencil: fbo.draw_buffer(fbo.no_color); fbo.depth_stencil = target_texture; break; default: fmt::throw_exception("Unsupported image aspect combination 0x%x", image_aspect_bits); } enable_depth_writes = (image_aspect_bits & m_write_aspect_mask) & gl::image_aspect::depth; enable_stencil_writes = (image_aspect_bits & m_write_aspect_mask) & gl::image_aspect::stencil; } if (!target_texture || fbo.check()) { // Save state (TODO) glGetIntegerv(GL_VIEWPORT, viewport); // Set initial state glViewport(region.x1, region.y1, region.width(), region.height()); cmd->color_maski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); cmd->depth_mask(image_aspect_bits == gl::image_aspect::color ? GL_FALSE : GL_TRUE); cmd->disable(GL_CULL_FACE); cmd->disable(GL_SCISSOR_TEST); cmd->clip_planes(GL_NONE); if (enable_depth_writes) { // Disabling depth test will also disable depth writes which is not desired cmd->depth_func(GL_ALWAYS); cmd->enable(GL_DEPTH_TEST); } else { cmd->disable(GL_DEPTH_TEST); } if (enable_stencil_writes) { // Disabling stencil test also disables stencil writes. cmd->enable(GL_STENCIL_TEST); cmd->stencil_mask(0xFF); cmd->stencil_func(GL_ALWAYS, 0xFF, 0xFF); cmd->stencil_op(GL_KEEP, GL_KEEP, GL_REPLACE); } else { cmd->disable(GL_STENCIL_TEST); } if (enable_blending) { cmd->enablei(GL_BLEND, 0); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); } else { cmd->disablei(GL_BLEND, 0); } // Render cmd->use_program(program_handle.id()); on_load(); bind_resources(); emit_geometry(); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); if (target_texture) { fbo.color[0] = GL_NONE; fbo.depth = GL_NONE; fbo.depth_stencil = GL_NONE; } } else { rsx_log.error("Overlay pass failed because framebuffer was not complete. Run with debug output enabled to diagnose the problem"); } } ui_overlay_renderer::ui_overlay_renderer() { vs_src = #include "../Program/GLSLSnippets/OverlayRenderVS.glsl" ; fs_src = #include "../Program/GLSLSnippets/OverlayRenderFS.glsl" ; vs_src = fmt::replace_all(vs_src, { { "#version 450", "#version 420" }, { "%preprocessor", "// %preprocessor" } }); fs_src = fmt::replace_all(fs_src, "%preprocessor", "// %preprocessor"); // Smooth filtering required for inputs m_input_filter = gl::filter::linear; } gl::texture_view* ui_overlay_renderer::load_simple_image(rsx::overlays::image_info* desc, bool temp_resource, u32 owner_uid) { auto tex = std::make_unique(GL_TEXTURE_2D, desc->w, desc->h, 1, 1, GL_RGBA8); tex->copy_from(desc->data, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8, {}); GLenum remap[] = { GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN }; auto view = std::make_unique(tex.get(), remap); auto result = view.get(); if (!temp_resource) { resources.push_back(std::move(tex)); view_cache[view_cache.size()] = std::move(view); } else { u64 key = reinterpret_cast(desc); temp_image_cache[key] = std::make_pair(owner_uid, std::move(tex)); temp_view_cache[key] = std::move(view); } return result; } void ui_overlay_renderer::create() { overlay_pass::create(); rsx::overlays::resource_config configuration; configuration.load_files(); for (const auto &res : configuration.texture_raw_data) { load_simple_image(res.get(), false, -1); } configuration.free_resources(); } void ui_overlay_renderer::destroy() { temp_image_cache.clear(); temp_view_cache.clear(); resources.clear(); font_cache.clear(); view_cache.clear(); overlay_pass::destroy(); } void ui_overlay_renderer::remove_temp_resources(u64 key) { std::vector keys_to_remove; for (const auto& temp_image : temp_image_cache) { if (temp_image.second.first == key) { keys_to_remove.push_back(temp_image.first); } } for (const auto& _key : keys_to_remove) { temp_image_cache.erase(_key); temp_view_cache.erase(_key); } } gl::texture_view* ui_overlay_renderer::find_font(rsx::overlays::font* font) { const auto font_size = font->get_glyph_data_dimensions(); u64 key = reinterpret_cast(font); auto found = view_cache.find(key); if (found != view_cache.end()) { if (const auto this_size = found->second->image()->size3D(); font_size.width == this_size.width && font_size.height == this_size.height && font_size.depth == this_size.depth) { return found->second.get(); } } // Create font file const std::vector glyph_data = font->get_glyph_data(); auto tex = std::make_unique(GL_TEXTURE_2D_ARRAY, font_size.width, font_size.height, font_size.depth, 1, GL_R8); tex->copy_from(glyph_data.data(), gl::texture::format::r, gl::texture::type::ubyte, {}); GLenum remap[] = { GL_RED, GL_RED, GL_RED, GL_RED }; auto view = std::make_unique(tex.get(), remap); auto result = view.get(); font_cache[key] = std::move(tex); view_cache[key] = std::move(view); return result; } gl::texture_view* ui_overlay_renderer::find_temp_image(rsx::overlays::image_info* desc, u32 owner_uid) { auto key = reinterpret_cast(desc); auto cached = temp_view_cache.find(key); if (cached != temp_view_cache.end()) { return cached->second.get(); } return load_simple_image(desc, true, owner_uid); } void ui_overlay_renderer::set_primitive_type(rsx::overlays::primitive_type type) { m_current_primitive_type = type; switch (type) { case rsx::overlays::primitive_type::quad_list: case rsx::overlays::primitive_type::triangle_strip: primitives = GL_TRIANGLE_STRIP; break; case rsx::overlays::primitive_type::line_list: primitives = GL_LINES; break; case rsx::overlays::primitive_type::line_strip: primitives = GL_LINE_STRIP; break; case rsx::overlays::primitive_type::triangle_fan: primitives = GL_TRIANGLE_FAN; break; default: fmt::throw_exception("Unexpected primitive type %d", static_cast(type)); } } void ui_overlay_renderer::emit_geometry() { if (m_current_primitive_type == rsx::overlays::primitive_type::quad_list) { // Emulate quads with disjointed triangle strips int num_quads = num_drawable_elements / 4; std::vector firsts; std::vector counts; firsts.resize(num_quads); counts.resize(num_quads); for (int n = 0; n < num_quads; ++n) { firsts[n] = (n * 4); counts[n] = 4; } int old_vao; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); m_vao.bind(); glMultiDrawArrays(GL_TRIANGLE_STRIP, firsts.data(), counts.data(), num_quads); glBindVertexArray(old_vao); } else { overlay_pass::emit_geometry(); } } void ui_overlay_renderer::run(gl::command_context& cmd_, const areau& viewport, GLuint target, rsx::overlays::overlay& ui) { program_handle.uniforms["viewport"] = color4f(static_cast(viewport.width()), static_cast(viewport.height()), static_cast(viewport.x1), static_cast(viewport.y1)); program_handle.uniforms["ui_scale"] = color4f(static_cast(ui.virtual_width), static_cast(ui.virtual_height), 1.f, 1.f); saved_sampler_state save_30(30, m_sampler); saved_sampler_state save_31(31, m_sampler); if (ui.status_flags & rsx::overlays::status_bits::invalidate_image_cache) { remove_temp_resources(ui.uid); ui.status_flags.clear(rsx::overlays::status_bits::invalidate_image_cache); } for (auto& cmd : ui.get_compiled().draw_commands) { set_primitive_type(cmd.config.primitives); upload_vertex_data(cmd.verts.data(), ::size32(cmd.verts)); num_drawable_elements = ::size32(cmd.verts); auto texture_mode = rsx::overlays::texture_sampling_mode::texture2D; switch (cmd.config.texture_ref) { case rsx::overlays::image_resource_id::game_icon: case rsx::overlays::image_resource_id::backbuffer: // TODO case rsx::overlays::image_resource_id::none: { texture_mode = rsx::overlays::texture_sampling_mode::none; cmd_->bind_texture(31, GL_TEXTURE_2D, GL_NONE); break; } case rsx::overlays::image_resource_id::raw_image: { cmd_->bind_texture(31, GL_TEXTURE_2D, find_temp_image(static_cast(cmd.config.external_data_ref), ui.uid)->id()); break; } case rsx::overlays::image_resource_id::font_file: { texture_mode = rsx::overlays::texture_sampling_mode::font3D; cmd_->bind_texture(30, GL_TEXTURE_2D_ARRAY, find_font(cmd.config.font_ref)->id()); break; } default: { cmd_->bind_texture(31, GL_TEXTURE_2D, view_cache[cmd.config.texture_ref - 1]->id()); break; } } rsx::overlays::vertex_options vert_opts; program_handle.uniforms["vertex_config"] = vert_opts .disable_vertex_snap(cmd.config.disable_vertex_snap) .get(); rsx::overlays::fragment_options draw_opts; program_handle.uniforms["fragment_config"] = draw_opts .texture_mode(texture_mode) .clip_fragments(cmd.config.clip_region) .pulse_glow(cmd.config.pulse_glow) .get(); program_handle.uniforms["timestamp"] = cmd.config.get_sinus_value(); program_handle.uniforms["albedo"] = cmd.config.color; program_handle.uniforms["clip_bounds"] = cmd.config.clip_rect; program_handle.uniforms["blur_intensity"] = static_cast(cmd.config.blur_strength); overlay_pass::run(cmd_, viewport, target, gl::image_aspect::color, true); } ui.update(); } video_out_calibration_pass::video_out_calibration_pass() { vs_src = #include "../Program/GLSLSnippets/GenericVSPassthrough.glsl" ; fs_src = "#version 420\n\n" "layout(binding=31) uniform sampler2D fs0;\n" "layout(binding=30) uniform sampler2D fs1;\n" "layout(location=0) in vec2 tc0;\n" "layout(location=0) out vec4 ocol;\n" "\n" "#define STEREO_MODE_DISABLED 0\n" "#define STEREO_MODE_ANAGLYPH 1\n" "#define STEREO_MODE_SIDE_BY_SIDE 2\n" "#define STEREO_MODE_OVER_UNDER 3\n" "\n" "vec2 sbs_single_matrix = vec2(2.0,0.4898f);\n" "vec2 sbs_multi_matrix = vec2(2.0,1.0);\n" "vec2 ou_single_matrix = vec2(1.0,0.9796f);\n" "vec2 ou_multi_matrix = vec2(1.0,2.0);\n" "\n" "uniform float gamma;\n" "uniform int limit_range;\n" "uniform int stereo_display_mode;\n" "uniform int stereo_image_count;\n" "\n" "vec4 read_source()\n" "{\n" " if (stereo_display_mode == STEREO_MODE_DISABLED) return texture(fs0, tc0);\n" "\n" " vec4 left, right;\n" " if (stereo_image_count == 1)\n" " {\n" " switch (stereo_display_mode)\n" " {\n" " case STEREO_MODE_ANAGLYPH:\n" " left = texture(fs0, tc0 * vec2(1.f, 0.4898f));\n" " right = texture(fs0, (tc0 * vec2(1.f, 0.4898f)) + vec2(0.f, 0.510204f));\n" " return vec4(left.r, right.g, right.b, 1.f);\n" " case STEREO_MODE_SIDE_BY_SIDE:\n" " if (tc0.x < 0.5) return texture(fs0, tc0* sbs_single_matrix);\n" " else return texture(fs0, (tc0* sbs_single_matrix) + vec2(-1.f, 0.510204f));\n" " case STEREO_MODE_OVER_UNDER:\n" " if (tc0.y < 0.5) return texture(fs0, tc0* ou_single_matrix);\n" " else return texture(fs0, (tc0* ou_single_matrix) + vec2(0.f, 0.020408f) );\n" " default:\n" // undefined behavior " return texture(fs0,tc0);\n" " }\n" " }\n" " else if (stereo_image_count == 2)\n" " {\n" " switch (stereo_display_mode)\n" " {\n" " case STEREO_MODE_ANAGLYPH:\n" " left = texture(fs0, tc0);\n" " right = texture(fs1, tc0);\n" " return vec4(left.r, right.g, right.b, 1.f);\n" " case STEREO_MODE_SIDE_BY_SIDE:\n" " if (tc0.x < 0.5) return texture(fs0,(tc0 * sbs_multi_matrix));\n" " else return texture(fs1,(tc0 * sbs_multi_matrix) + vec2(-1.f,0.f));\n" " case STEREO_MODE_OVER_UNDER:\n" " if (tc0.y < 0.5) return texture(fs0,(tc0 * ou_multi_matrix));\n" " else return texture(fs1,(tc0 * ou_multi_matrix) + vec2(0.f,-1.f));\n" " default:\n" // undefined behavior " return texture(fs0,tc0);\n" " }\n" " }\n" " {\n" " vec2 coord_left = tc0 * vec2(1.f, 0.4898f);\n" " vec2 coord_right = coord_left + vec2(0.f, 0.510204f);\n" " left = texture(fs0, coord_left);\n" " right = texture(fs0, coord_right);\n" " return vec4(left.r, right.g, right.b, 1.);\n" " }\n" "}\n" "\n" "void main()\n" "{\n" " vec4 color = read_source();\n" " color.rgb = pow(color.rgb, vec3(gamma));\n" " if (limit_range > 0)\n" " ocol = ((color * 220.) + 16.) / 255.;\n" " else\n" " ocol = color;\n" "}\n"; m_input_filter = gl::filter::linear; } void video_out_calibration_pass::run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, stereo_render_mode_options stereo_mode, gl::filter input_filter) { if (m_input_filter != input_filter) { m_input_filter = input_filter; m_sampler.set_parameteri(GL_TEXTURE_MIN_FILTER, static_cast(m_input_filter)); m_sampler.set_parameteri(GL_TEXTURE_MAG_FILTER, static_cast(m_input_filter)); } program_handle.uniforms["gamma"] = gamma; program_handle.uniforms["limit_range"] = limited_rgb + 0; program_handle.uniforms["stereo_display_mode"] = static_cast(stereo_mode); program_handle.uniforms["stereo_image_count"] = (source[1] == GL_NONE? 1 : 2); saved_sampler_state saved(31, m_sampler); cmd->bind_texture(31, GL_TEXTURE_2D, source[0]); saved_sampler_state saved2(30, m_sampler); cmd->bind_texture(30, GL_TEXTURE_2D, source[1]); overlay_pass::run(cmd, viewport, GL_NONE, gl::image_aspect::color, false); } rp_ssbo_to_generic_texture::rp_ssbo_to_generic_texture() { vs_src = #include "../Program/GLSLSnippets/GenericVSPassthrough.glsl" ; fs_src = #include "../Program/GLSLSnippets/CopyBufferToGenericImage.glsl" ; const auto& caps = gl::get_driver_caps(); const bool stencil_export_supported = caps.ARB_shader_stencil_export_supported; const bool legacy_format_support = caps.subvendor_ATI; std::pair repl_list[] = { { "%set, ", "" }, { "%loc", std::to_string(GL_COMPUTE_BUFFER_SLOT(0)) }, { "%push_block", fmt::format("binding=%d, std140", GL_COMPUTE_BUFFER_SLOT(1)) }, { "%stencil_export_supported", stencil_export_supported ? "1" : "0" }, { "%legacy_format_support", legacy_format_support ? "1" : "0" } }; fs_src = fmt::replace_all(fs_src, repl_list); if (stencil_export_supported) { m_write_aspect_mask |= gl::image_aspect::stencil; } } void rp_ssbo_to_generic_texture::run(gl::command_context& cmd, const buffer* src, const texture_view* dst, const u32 src_offset, const coordu& dst_region, const pixel_buffer_layout& layout) { const u32 bpp = dst->image()->pitch() / dst->image()->width(); const u32 row_length = utils::align(dst_region.width * bpp, std::max(layout.alignment, 1)) / bpp; program_handle.uniforms["src_pitch"] = row_length; program_handle.uniforms["swap_bytes"] = layout.swap_bytes; program_handle.uniforms["format"] = static_cast(dst->image()->get_internal_format()); src->bind_range(gl::buffer::target::ssbo, GL_COMPUTE_BUFFER_SLOT(0), src_offset, row_length * bpp * dst_region.height); cmd->stencil_mask(0xFF); overlay_pass::run(cmd, dst_region, dst->id(), dst->aspect()); } void rp_ssbo_to_generic_texture::run(gl::command_context& cmd, const buffer* src, texture* dst, const u32 src_offset, const coordu& dst_region, const pixel_buffer_layout& layout) { gl::nil_texture_view view(dst); run(cmd, src, &view, src_offset, dst_region, layout); } }