1212#include " yuzu_common/logging/log.h"
1313#include " yuzu_common/microprofile.h"
1414#include " yuzu_common/settings.h"
15+ #include " yuzu_common/stb.h"
1516#include " core/core_timing.h"
1617#include " frontend/emu_window.h"
1718#include " yuzu_video_core/capture.h"
2223#include " yuzu_video_core/renderer_opengl/gl_shader_util.h"
2324#include " yuzu_video_core/renderer_opengl/renderer_opengl.h"
2425#include " yuzu_video_core/textures/decoders.h"
26+ #include " yuzu_video_core/watermark.h"
2527
2628namespace OpenGL {
2729namespace {
@@ -94,7 +96,10 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
9496 : RendererBase{emu_window_, std::move (context_)},
9597 emu_window{emu_window_}, device_memory{device_memory_}, gpu{gpu_}, device{emu_window_},
9698 state_tracker{}, program_manager{device},
97- rasterizer (emu_window, gpu, device_memory, device, program_manager, state_tracker) {
99+ rasterizer (emu_window, gpu, device_memory, device, program_manager, state_tracker),
100+ watermark_texture (0 ), watermark_vao(0 ), watermark_vbo(0 ), watermark_shader(0 ), watermark_width(0 ), watermark_height(0 ),
101+ watermark_timer_started (false ), watermark_show(true )
102+ {
98103 if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
99104 glEnable (GL_DEBUG_OUTPUT );
100105 glEnable (GL_DEBUG_OUTPUT_SYNCHRONOUS );
@@ -128,9 +133,14 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
128133 glBindRenderbuffer (GL_RENDERBUFFER , capture_renderbuffer.handle );
129134 glRenderbufferStorage (GL_RENDERBUFFER , GL_SRGB8 , VideoCore::Capture::LinearWidth,
130135 VideoCore::Capture::LinearHeight);
136+
137+ InitializeWatermark ();
131138}
132139
133- RendererOpenGL::~RendererOpenGL () = default ;
140+ RendererOpenGL::~RendererOpenGL ()
141+ {
142+ CleanupWatermark ();
143+ }
134144
135145void RendererOpenGL::Composite (std::span<const Tegra::FramebufferConfig> framebuffers) {
136146 if (framebuffers.empty ()) {
@@ -142,13 +152,18 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
142152
143153 state_tracker.BindFramebuffer (0 );
144154 blit_screen->DrawScreen (framebuffers, emu_window.GetFramebufferLayout (), false );
155+ if (watermark_show)
156+ {
157+ RenderWatermark ();
158+ }
145159
146160 ++m_current_frame;
147161
148162 gpu.RendererFrameEndNotify ();
149163 rasterizer.TickFrame ();
150164
151165 context->SwapBuffers ();
166+
152167 render_window.OnFrameDisplayed ();
153168}
154169
@@ -162,10 +177,253 @@ void RendererOpenGL::AddTelemetryFields() {
162177 LOG_INFO (Render_OpenGL, " GL_RENDERER: {}" , gpu_model);
163178}
164179
180+ void RendererOpenGL::InitializeWatermark ()
181+ {
182+ while (glGetError () != GL_NO_ERROR ) {}
183+
184+ int channels;
185+ unsigned char * image_data = stbi_load_from_memory (watermark_png, sizeof (watermark_png), &watermark_width, &watermark_height, &channels, 4 );
186+ if (!image_data)
187+ {
188+ LOG_ERROR (Render_OpenGL, " Failed to load watermark image: {}" , stbi_failure_reason ());
189+ return ;
190+ }
191+
192+ glGenTextures (1 , &watermark_texture);
193+ glBindTexture (GL_TEXTURE_2D , watermark_texture);
194+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR );
195+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR );
196+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE );
197+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE );
198+ glTexImage2D (GL_TEXTURE_2D , 0 , GL_RGBA , watermark_width, watermark_height, 0 , GL_RGBA , GL_UNSIGNED_BYTE , image_data);
199+ stbi_image_free (image_data);
200+
201+ glGenVertexArrays (1 , &watermark_vao);
202+ glGenBuffers (1 , &watermark_vbo);
203+
204+ glBindVertexArray (watermark_vao);
205+ glBindBuffer (GL_ARRAY_BUFFER , watermark_vbo);
206+
207+ const size_t vertex_data_size = 6 * 4 * sizeof (float );
208+ glBufferData (GL_ARRAY_BUFFER , vertex_data_size, nullptr , GL_DYNAMIC_DRAW );
209+
210+ glVertexAttribPointer (0 , 2 , GL_FLOAT , GL_FALSE , 4 * sizeof (float ), (void *)0 );
211+ glEnableVertexAttribArray (0 );
212+ glVertexAttribPointer (1 , 2 , GL_FLOAT , GL_FALSE , 4 * sizeof (float ), (void *)(2 * sizeof (float )));
213+ glEnableVertexAttribArray (1 );
214+
215+ const std::string vertex_shader_source = R"(
216+ #version 330 core
217+ layout (location = 0) in vec2 aPos;
218+ layout (location = 1) in vec2 aTexCoord;
219+
220+ out vec2 TexCoord;
221+
222+ void main() {
223+ gl_Position = vec4(aPos, 0.0, 1.0);
224+ TexCoord = aTexCoord;
225+ }
226+ )" ;
227+
228+ const std::string fragment_shader_source = R"(
229+ #version 330 core
230+ in vec2 TexCoord;
231+ out vec4 FragColor;
232+
233+ uniform sampler2D watermarkTexture;
234+ uniform float alpha;
235+
236+ void main() {
237+ vec4 texColor = texture(watermarkTexture, TexCoord);
238+ FragColor = vec4(texColor.rgb, texColor.a * alpha);
239+ }
240+ )" ;
241+
242+ GLuint vertex_shader = glCreateShader (GL_VERTEX_SHADER );
243+ const char * vertex_src = vertex_shader_source.c_str ();
244+ glShaderSource (vertex_shader, 1 , &vertex_src, nullptr );
245+ glCompileShader (vertex_shader);
246+
247+ GLint success;
248+ glGetShaderiv (vertex_shader, GL_COMPILE_STATUS , &success);
249+ if (!success)
250+ {
251+ char info_log[512 ];
252+ glGetShaderInfoLog (vertex_shader, 512 , nullptr , info_log);
253+ LOG_ERROR (Render_OpenGL, " Vertex shader compilation failed: {}" , info_log);
254+ return ;
255+ }
256+
257+ GLuint fragment_shader = glCreateShader (GL_FRAGMENT_SHADER );
258+ const char * fragment_src = fragment_shader_source.c_str ();
259+ glShaderSource (fragment_shader, 1 , &fragment_src, nullptr );
260+ glCompileShader (fragment_shader);
261+
262+ glGetShaderiv (fragment_shader, GL_COMPILE_STATUS , &success);
263+ if (!success) {
264+ char info_log[512 ];
265+ glGetShaderInfoLog (fragment_shader, 512 , nullptr , info_log);
266+ LOG_ERROR (Render_OpenGL, " Fragment shader compilation failed: {}" , info_log);
267+ glDeleteShader (vertex_shader);
268+ return ;
269+ }
270+
271+ watermark_shader = glCreateProgram ();
272+ glAttachShader (watermark_shader, vertex_shader);
273+ glAttachShader (watermark_shader, fragment_shader);
274+ glLinkProgram (watermark_shader);
275+
276+ glGetProgramiv (watermark_shader, GL_LINK_STATUS , &success);
277+ if (!success)
278+ {
279+ char info_log[512 ];
280+ glGetProgramInfoLog (watermark_shader, 512 , nullptr , info_log);
281+ LOG_ERROR (Render_OpenGL, " Shader program linking failed: {}" , info_log);
282+ watermark_shader = 0 ;
283+ }
284+ glDeleteShader (vertex_shader);
285+ glDeleteShader (fragment_shader);
286+
287+ glBindVertexArray (0 );
288+ glBindBuffer (GL_ARRAY_BUFFER , 0 );
289+ glBindTexture (GL_TEXTURE_2D , 0 );
290+ }
291+
292+ void RendererOpenGL::RenderWatermark ()
293+ {
294+ if (!watermark_texture || !watermark_shader || !watermark_vao || !watermark_vbo)
295+ {
296+ return ;
297+ }
298+
299+ if (!watermark_timer_started)
300+ {
301+ watermark_start_time = std::chrono::steady_clock::now ();
302+ watermark_timer_started = true ;
303+ }
304+
305+ std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now ();
306+ long long elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(current_time - watermark_start_time).count ();
307+
308+ float fade_alpha;
309+ const float fade_duration = 300 .0f ;
310+ if (elapsed_seconds >= fade_duration)
311+ {
312+ fade_alpha = 0 .0f ;
313+ watermark_show = false ;
314+ }
315+ else
316+ {
317+ float progress = elapsed_seconds / fade_duration;
318+ float eased = 0 .5f * (1 .0f + cosf (progress * 3 .14159f ));
319+ fade_alpha = 0 .8f * eased;
320+ }
321+
322+ GLint current_program, current_vao, current_buffer, current_texture, current_active_texture;
323+ glGetIntegerv (GL_CURRENT_PROGRAM , ¤t_program);
324+ glGetIntegerv (GL_VERTEX_ARRAY_BINDING , ¤t_vao);
325+ glGetIntegerv (GL_ARRAY_BUFFER_BINDING , ¤t_buffer);
326+ glGetIntegerv (GL_TEXTURE_BINDING_2D , ¤t_texture);
327+ glGetIntegerv (GL_ACTIVE_TEXTURE , ¤t_active_texture);
328+
329+ glUseProgram (0 );
330+ glBindVertexArray (0 );
331+ glBindBuffer (GL_ARRAY_BUFFER , 0 );
332+ glActiveTexture (GL_TEXTURE0 );
333+ glBindTexture (GL_TEXTURE_2D , 0 );
334+
335+ glDisable (GL_DEPTH_TEST );
336+ glDisable (GL_CULL_FACE );
337+ glDisable (GL_STENCIL_TEST );
338+ glDisable (GL_SCISSOR_TEST );
339+ glEnable (GL_BLEND );
340+ glBlendFunc (GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA );
341+
342+ const Layout::FramebufferLayout & layout = emu_window.GetFramebufferLayout ();
343+ float watermark_scale = 0 .14f ;
344+ float aspect_ratio = (float )watermark_height / (float )watermark_width;
345+ float watermark_screen_width = layout.width * watermark_scale;
346+ float watermark_screen_height = watermark_screen_width * aspect_ratio;
347+
348+ float padding = 18 .0f ;
349+ float x = layout.width - watermark_screen_width - padding;
350+ float y = padding;
351+
352+ float ndc_left = (x / layout.width ) * 2 .0f - 1 .0f ;
353+ float ndc_right = ((x + watermark_screen_width) / layout.width ) * 2 .0f - 1 .0f ;
354+ float ndc_bottom = (y / layout.height ) * 2 .0f - 1 .0f ;
355+ float ndc_top = ((y + watermark_screen_height) / layout.height ) * 2 .0f - 1 .0f ;
356+
357+ float vertices[] = {
358+ ndc_left, ndc_bottom, 0 .0f , 1 .0f , // Bottom-left
359+ ndc_right, ndc_bottom, 1 .0f , 1 .0f , // Bottom-right
360+ ndc_left, ndc_top, 0 .0f , 0 .0f , // Top-left
361+
362+ ndc_right, ndc_bottom, 1 .0f , 1 .0f , // Bottom-right
363+ ndc_right, ndc_top, 1 .0f , 0 .0f , // Top-right
364+ ndc_left, ndc_top, 0 .0f , 0 .0f // Top-left
365+ };
366+
367+ glUseProgram (watermark_shader);
368+ glBindVertexArray (watermark_vao);
369+ glBindBuffer (GL_ARRAY_BUFFER , watermark_vbo);
370+ glBufferSubData (GL_ARRAY_BUFFER , 0 , sizeof (vertices), vertices);
371+ glActiveTexture (GL_TEXTURE0 );
372+ glBindTexture (GL_TEXTURE_2D , watermark_texture);
373+
374+ GLint tex_uniform = glGetUniformLocation (watermark_shader, " watermarkTexture" );
375+ GLint alpha_uniform = glGetUniformLocation (watermark_shader, " alpha" );
376+ if (tex_uniform != -1 )
377+ {
378+ glUniform1i (tex_uniform, 0 );
379+ }
380+
381+ if (alpha_uniform != -1 )
382+ {
383+ glUniform1f (alpha_uniform, fade_alpha);
384+ }
385+
386+ while (glGetError () != GL_NO_ERROR ) {}
387+
388+ glDrawArrays (GL_TRIANGLES , 0 , 6 );
389+ glUseProgram (current_program);
390+ glBindVertexArray (current_vao);
391+ glBindBuffer (GL_ARRAY_BUFFER , current_buffer);
392+ glActiveTexture (current_active_texture);
393+ glBindTexture (GL_TEXTURE_2D , current_texture);
394+ }
395+
396+ void RendererOpenGL::CleanupWatermark ()
397+ {
398+ if (watermark_texture)
399+ {
400+ glDeleteTextures (1 , &watermark_texture);
401+ watermark_texture = 0 ;
402+ }
403+
404+ if (watermark_shader)
405+ {
406+ glDeleteProgram (watermark_shader);
407+ watermark_shader = 0 ;
408+ }
409+
410+ if (watermark_vao)
411+ {
412+ glDeleteVertexArrays (1 , &watermark_vao);
413+ watermark_vao = 0 ;
414+ }
415+
416+ if (watermark_vbo)
417+ {
418+ glDeleteBuffers (1 , &watermark_vbo);
419+ watermark_vbo = 0 ;
420+ }
421+ }
422+
165423void RendererOpenGL::RenderToBuffer (std::span<const Tegra::FramebufferConfig> framebuffers,
166424 const Layout::FramebufferLayout& layout, void * dst) {
167425 GLint old_read_fb;
168- GLint old_draw_fb;
426+ GLint old_draw_fb;
169427 glGetIntegerv (GL_READ_FRAMEBUFFER_BINDING , &old_read_fb);
170428 glGetIntegerv (GL_DRAW_FRAMEBUFFER_BINDING , &old_draw_fb);
171429
0 commit comments