From c22816591dc7a3ef46ff66d660aaf9e5836f7b58 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Thu, 23 Apr 2026 20:03:42 -0700 Subject: [PATCH 01/12] implemented cursor draw in opengl --- .../host_shaders/opengl_present.frag | 44 ++++++++++++++++++- .../host_shaders/opengl_present.vert | 4 +- .../renderer_opengl/renderer_opengl.cpp | 22 ++++++---- .../renderer_opengl/renderer_opengl.h | 3 ++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag index 3285301c8..067eaecb4 100644 --- a/src/video_core/host_shaders/opengl_present.frag +++ b/src/video_core/host_shaders/opengl_present.frag @@ -11,8 +11,48 @@ layout(binding = 0) uniform sampler2D color_texture; uniform vec4 i_resolution; uniform vec4 o_resolution; +uniform vec2 cursor_pos; +uniform bool cursor_enable; uniform int layer; - +in vec2 pixelUnit; void main() { - color = texture(color_texture, frag_tex_coord); + vec4 pixel = texture(color_texture, frag_tex_coord); + vec2 rfrag_tex_coord = vec2(frag_tex_coord.yx); + //Cursor + if (cursor_enable){ + //Black Outline + if (rfrag_tex_coord.x <= (cursor_pos.x + (2.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (1.0*pixelUnit.x))) { + if (rfrag_tex_coord.y <= (cursor_pos.y + (5.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (4.0*pixelUnit.y))) { + pixel = vec4(0.0, 0.0, 0.0, 1.0); + } + } + + if (rfrag_tex_coord.y <= (cursor_pos.y + (2.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (1.0*pixelUnit.y))) { + if (rfrag_tex_coord.x <= (cursor_pos.x + (5.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (4.0*pixelUnit.x))) { + pixel = vec4(0.0, 0.0, 0.0, 1.0); + } + } + //White Cross + if (rfrag_tex_coord.x <= (cursor_pos.x + (1.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (0.0*pixelUnit.x))) { + if (rfrag_tex_coord.y <= (cursor_pos.y + (4.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (3.0*pixelUnit.y))) { + pixel = vec4(1.0, 1.0, 1.0, 1.0); + } + } + + if (rfrag_tex_coord.y <= (cursor_pos.y + (1.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (0.0*pixelUnit.y))) { + if (rfrag_tex_coord.x <= (cursor_pos.x + (4.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (3.0*pixelUnit.x))) { + pixel = vec4(1.0, 1.0, 1.0, 1.0); + } + } + } + + color = vec4(pixel.rgb, 1.0); } diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert index f2a7a0b11..8e00a5be9 100644 --- a/src/video_core/host_shaders/opengl_present.vert +++ b/src/video_core/host_shaders/opengl_present.vert @@ -13,11 +13,13 @@ layout(location = 0) out vec2 frag_tex_coord; // The third row could be used for projection, which we don't need in 2D. It hence is assumed to // implicitly be [0, 0, 1] uniform mat3x2 modelview_matrix; - +out vec2 pixelUnit; void main() { // Multiply input position by the rotscale part of the matrix and then manually translate by // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector // to `vec3(vert_position.xy, 1.0)` gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); frag_tex_coord = vert_tex_coord; + pixelUnit.x = 1/320.0; + pixelUnit.y = 1/240.0; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index c8cb6c000..1274c08dc 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -433,6 +433,8 @@ void RendererOpenGL::ReloadShader(Settings::StereoRenderOption render_3d) { uniform_i_resolution = glGetUniformLocation(shader.handle, "i_resolution"); uniform_o_resolution = glGetUniformLocation(shader.handle, "o_resolution"); uniform_layer = glGetUniformLocation(shader.handle, "layer"); + uniform_cursor_pos = glGetUniformLocation(shader.handle, "cursor_pos"); + uniform_cursor_enable = glGetUniformLocation(shader.handle, "cursor_enable"); attrib_position = glGetAttribLocation(shader.handle, "vert_position"); attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); } @@ -556,11 +558,13 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl const u32 scale_factor = GetResolutionScaleFactor(); const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle; - glUniform4f(uniform_i_resolution, static_cast(screen_info.texture.width * scale_factor), - static_cast(screen_info.texture.height * scale_factor), - 1.0f / static_cast(screen_info.texture.width * scale_factor), - 1.0f / static_cast(screen_info.texture.height * scale_factor)); - glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); + glUniform4f(uniform_i_resolution, static_cast(screen_info.texture.height * scale_factor), + static_cast(screen_info.texture.width * scale_factor), + 1.0f / static_cast(screen_info.texture.height * scale_factor), + 1.0f / static_cast(screen_info.texture.width * scale_factor)); + glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); + glUniform1i(uniform_cursor_enable, 1); + glUniform2f(uniform_cursor_pos, 159.0f/320.0f, 119.0f/240.0f); state.texture_units[0].texture_2d = screen_info.display_texture; state.texture_units[0].sampler = sampler; state.Apply(); @@ -627,11 +631,11 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const u32 scale_factor = GetResolutionScaleFactor(); const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle; glUniform4f(uniform_i_resolution, - static_cast(screen_info_l.texture.width * scale_factor), static_cast(screen_info_l.texture.height * scale_factor), - 1.0f / static_cast(screen_info_l.texture.width * scale_factor), - 1.0f / static_cast(screen_info_l.texture.height * scale_factor)); - glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); + static_cast(screen_info_l.texture.width * scale_factor), + 1.0f / static_cast(screen_info_l.texture.height * scale_factor), + 1.0f / static_cast(screen_info_l.texture.width * scale_factor)); + glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); state.texture_units[0].texture_2d = screen_info_l.display_texture; state.texture_units[1].texture_2d = screen_info_r.display_texture; state.texture_units[0].sampler = sampler; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 2f2b318ed..264b3c4fe 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -107,6 +107,9 @@ private: GLuint uniform_o_resolution; GLuint uniform_layer; + // Shader uniform for onscreen cursor + GLuint uniform_cursor_pos; + GLuint uniform_cursor_enable; // Shader attribute input indices GLuint attrib_position; GLuint attrib_tex_coord; From ff127ce517c23d3b7d17a5195364f3e9e392b667 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Thu, 23 Apr 2026 20:28:54 -0700 Subject: [PATCH 02/12] enabled cursor only on bottom screen --- src/video_core/renderer_opengl/renderer_opengl.cpp | 9 +++++++-- src/video_core/renderer_opengl/renderer_opengl.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 1274c08dc..45dd4d5d4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -563,7 +563,11 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl 1.0f / static_cast(screen_info.texture.height * scale_factor), 1.0f / static_cast(screen_info.texture.width * scale_factor)); glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); - glUniform1i(uniform_cursor_enable, 1); + if (currScreenDraw == 1){ + glUniform1i(uniform_cursor_enable, 1); + } else { + glUniform1i(uniform_cursor_enable, 0); + } glUniform2f(uniform_cursor_pos, 159.0f/320.0f, 119.0f/240.0f); state.texture_units[0].texture_2d = screen_info.display_texture; state.texture_units[0].sampler = sampler; @@ -740,6 +744,7 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout, if (!layout.top_screen_enabled) { return; } + currScreenDraw = 0; int leftside, rightside; leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0; rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1; @@ -801,7 +806,7 @@ void RendererOpenGL::DrawBottomScreen(const Layout::FramebufferLayout& layout, if (!layout.bottom_screen_enabled) { return; } - + currScreenDraw = 1; const float bottom_screen_left = static_cast(bottom_screen.left); const float bottom_screen_top = static_cast(bottom_screen.top); const float bottom_screen_width = static_cast(bottom_screen.GetWidth()); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 264b3c4fe..68f0c260d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -113,6 +113,7 @@ private: // Shader attribute input indices GLuint attrib_position; GLuint attrib_tex_coord; + int currScreenDraw; // 0 is Top, 1 is Bottom FrameDumperOpenGL frame_dumper; }; From 2dfab0ac69b593d5cdc8165d61945486e3ac6db3 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Thu, 23 Apr 2026 20:59:13 -0700 Subject: [PATCH 03/12] fixed rfrag_tex_coord y axis, prototyping cursor position --- src/video_core/host_shaders/opengl_present.frag | 2 +- src/video_core/renderer_opengl/renderer_opengl.cpp | 4 +++- src/video_core/renderer_opengl/renderer_opengl.h | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag index 067eaecb4..ef8610a34 100644 --- a/src/video_core/host_shaders/opengl_present.frag +++ b/src/video_core/host_shaders/opengl_present.frag @@ -17,7 +17,7 @@ uniform int layer; in vec2 pixelUnit; void main() { vec4 pixel = texture(color_texture, frag_tex_coord); - vec2 rfrag_tex_coord = vec2(frag_tex_coord.yx); + vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, 1.0 - frag_tex_coord.x); //Cursor if (cursor_enable){ //Black Outline diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 45dd4d5d4..441475838 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -568,7 +568,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl } else { glUniform1i(uniform_cursor_enable, 0); } - glUniform2f(uniform_cursor_pos, 159.0f/320.0f, 119.0f/240.0f); + glUniform2f(uniform_cursor_pos, cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); state.texture_units[0].texture_2d = screen_info.display_texture; state.texture_units[0].sampler = sampler; state.Apply(); @@ -700,6 +700,8 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f } glUniform1i(uniform_layer, 0); + cursor_pos[0] += 1; + cursor_pos[1] += 1; if (!Settings::values.swap_screen.GetValue()) { DrawTopScreen(layout, top_screen); glUniform1i(uniform_layer, 0); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 68f0c260d..a6c56072b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -114,6 +114,7 @@ private: GLuint attrib_position; GLuint attrib_tex_coord; int currScreenDraw; // 0 is Top, 1 is Bottom + std::array cursor_pos = {0,0}; FrameDumperOpenGL frame_dumper; }; From e489fac61b61996666d3197db86acd655819ca33 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Fri, 24 Apr 2026 08:54:20 -0700 Subject: [PATCH 04/12] added uniforms for vulkan --- .../host_shaders/vulkan_present.frag | 2 ++ .../host_shaders/vulkan_present.vert | 2 ++ .../host_shaders/vulkan_present_anaglyph.frag | 2 ++ .../vulkan_present_interlaced.frag | 2 ++ .../renderer_opengl/renderer_opengl.cpp | 6 ++++ .../renderer_vulkan/renderer_vulkan.cpp | 35 +++++++++++++------ .../renderer_vulkan/renderer_vulkan.h | 6 +++- 7 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/video_core/host_shaders/vulkan_present.frag b/src/video_core/host_shaders/vulkan_present.frag index 41c0916bd..01b158732 100644 --- a/src/video_core/host_shaders/vulkan_present.frag +++ b/src/video_core/host_shaders/vulkan_present.frag @@ -12,6 +12,8 @@ layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; vec4 o_resolution; + vec2 cursor_pos; + int cursor_enable; int screen_id_l; int screen_id_r; int layer; diff --git a/src/video_core/host_shaders/vulkan_present.vert b/src/video_core/host_shaders/vulkan_present.vert index 08b4d252c..2844fdafc 100644 --- a/src/video_core/host_shaders/vulkan_present.vert +++ b/src/video_core/host_shaders/vulkan_present.vert @@ -13,6 +13,8 @@ layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; vec4 o_resolution; + vec2 cursor_pos; + int cursor_enable; int screen_id_l; int screen_id_r; int layer; diff --git a/src/video_core/host_shaders/vulkan_present_anaglyph.frag b/src/video_core/host_shaders/vulkan_present_anaglyph.frag index a01ce63f6..2d768d87a 100644 --- a/src/video_core/host_shaders/vulkan_present_anaglyph.frag +++ b/src/video_core/host_shaders/vulkan_present_anaglyph.frag @@ -24,6 +24,8 @@ layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; vec4 o_resolution; + vec2 cursor_pos; + int cursor_enable; int screen_id_l; int screen_id_r; int layer; diff --git a/src/video_core/host_shaders/vulkan_present_interlaced.frag b/src/video_core/host_shaders/vulkan_present_interlaced.frag index b033a9dec..b150413f1 100644 --- a/src/video_core/host_shaders/vulkan_present_interlaced.frag +++ b/src/video_core/host_shaders/vulkan_present_interlaced.frag @@ -12,6 +12,8 @@ layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; vec4 o_resolution; + vec2 cursor_pos; + int cursor_enable; int screen_id_l; int screen_id_r; int layer; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 441475838..d0eaad708 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -640,6 +640,12 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l, 1.0f / static_cast(screen_info_l.texture.height * scale_factor), 1.0f / static_cast(screen_info_l.texture.width * scale_factor)); glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); + if (currScreenDraw == 1){ + glUniform1i(uniform_cursor_enable, 1); + } else { + glUniform1i(uniform_cursor_enable, 0); + } + glUniform2f(uniform_cursor_pos, cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); state.texture_units[0].texture_2d = screen_info_l.display_texture; state.texture_units[1].texture_2d = screen_info_r.display_texture; state.texture_units[0].sampler = sampler; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 0a25c2036..7a00ed342 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -783,11 +783,17 @@ void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w, const u32 scale_factor = GetResolutionScaleFactor(); draw_info.i_resolution = - Common::MakeVec(static_cast(screen_info.texture.width * scale_factor), - static_cast(screen_info.texture.height * scale_factor), - 1.0f / static_cast(screen_info.texture.width * scale_factor), - 1.0f / static_cast(screen_info.texture.height * scale_factor)); - draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w); + Common::MakeVec(static_cast(screen_info.texture.height * scale_factor), + static_cast(screen_info.texture.width * scale_factor), + 1.0f / static_cast(screen_info.texture.height * scale_factor), + 1.0f / static_cast(screen_info.texture.width * scale_factor)); + draw_info.o_resolution = Common::MakeVec(w, h, 1.0f / w, 1.0f / h); + if (currScreenDraw == 1){ + draw_info.cursor_enable = 1; + } else { + draw_info.cursor_enable = 0; + } + draw_info.cursor_pos = Common::MakeVec(cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); draw_info.screen_id_l = screen_id; scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) { @@ -855,11 +861,17 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl const u32 scale_factor = GetResolutionScaleFactor(); draw_info.i_resolution = - Common::MakeVec(static_cast(screen_info_l.texture.width * scale_factor), - static_cast(screen_info_l.texture.height * scale_factor), - 1.0f / static_cast(screen_info_l.texture.width * scale_factor), - 1.0f / static_cast(screen_info_l.texture.height * scale_factor)); - draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w); + Common::MakeVec(static_cast(screen_info_l.texture.height * scale_factor), + static_cast(screen_info_l.texture.width * scale_factor), + 1.0f / static_cast(screen_info_l.texture.height * scale_factor), + 1.0f / static_cast(screen_info_l.texture.width * scale_factor)); + draw_info.o_resolution = Common::MakeVec(w, h, 1.0f / w, 1.0f / h); + if (currScreenDraw == 1){ + draw_info.cursor_enable = 1; + } else { + draw_info.cursor_enable = 0; + } + draw_info.cursor_pos = Common::MakeVec(cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); draw_info.screen_id_l = screen_id_l; draw_info.screen_id_r = screen_id_r; @@ -886,6 +898,7 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout, if (!layout.top_screen_enabled) { return; } + currScreenDraw = 0; int leftside, rightside; leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0; rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1; @@ -944,7 +957,7 @@ void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout, if (!layout.bottom_screen_enabled) { return; } - + currScreenDraw = 1; const float bottom_screen_left = static_cast(bottom_screen.left); const float bottom_screen_top = static_cast(bottom_screen.top); const float bottom_screen_width = static_cast(bottom_screen.GetWidth()); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 14c9bd34f..481d76187 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -58,12 +58,14 @@ struct PresentUniformData { std::array modelview; Common::Vec4f i_resolution; Common::Vec4f o_resolution; + Common::Vec2f cursor_pos; + int cursor_enable = 0; int screen_id_l = 0; int screen_id_r = 0; int layer = 0; int reverse_interlaced = 0; }; -static_assert(sizeof(PresentUniformData) == 112, +static_assert(sizeof(PresentUniformData) == 124, "PresentUniformData does not structure in shader!"); class RendererVulkan : public VideoCore::RendererBase { @@ -151,6 +153,8 @@ private: vk::ShaderModule cursor_fragment_shader{}; vk::Pipeline cursor_pipeline{}; vk::UniquePipelineLayout cursor_pipeline_layout{}; + int currScreenDraw; // 0 is Top, 1 is Bottom + std::array cursor_pos = {0,0}; }; } // namespace Vulkan From f83e60243e6efd930f6c5e18019e6f98cfa9e278 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Fri, 24 Apr 2026 13:27:49 -0700 Subject: [PATCH 05/12] added cursor to vulkan --- .../host_shaders/vulkan_present.frag | 42 ++++++++++++++++++- .../host_shaders/vulkan_present.vert | 4 +- .../renderer_vulkan/renderer_vulkan.cpp | 3 +- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/video_core/host_shaders/vulkan_present.frag b/src/video_core/host_shaders/vulkan_present.frag index 01b158732..54d61ea8d 100644 --- a/src/video_core/host_shaders/vulkan_present.frag +++ b/src/video_core/host_shaders/vulkan_present.frag @@ -7,7 +7,7 @@ layout (location = 0) in vec2 frag_tex_coord; layout (location = 0) out vec4 color; - +layout (location = 1) in vec2 pixelUnit; layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; @@ -22,6 +22,7 @@ layout (push_constant, std140) uniform DrawInfo { layout (set = 0, binding = 0) uniform sampler2D screen_textures[3]; + vec4 GetScreen(int screen_id) { #ifdef ARRAY_DYNAMIC_INDEX return texture(screen_textures[screen_id], frag_tex_coord); @@ -38,5 +39,42 @@ vec4 GetScreen(int screen_id) { } void main() { - color = GetScreen(screen_id_l); + vec4 pixel = GetScreen(screen_id_l); + vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, 1.0 - frag_tex_coord.x); + //Cursor + if (cursor_enable == 1){ + //Black Outline + if (rfrag_tex_coord.x <= (cursor_pos.x + (2.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (1.0*pixelUnit.x))) { + if (rfrag_tex_coord.y <= (cursor_pos.y + (5.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (4.0*pixelUnit.y))) { + pixel = vec4(0.0, 0.0, 0.0, 1.0); + } + } + + if (rfrag_tex_coord.y <= (cursor_pos.y + (2.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (1.0*pixelUnit.y))) { + if (rfrag_tex_coord.x <= (cursor_pos.x + (5.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (4.0*pixelUnit.x))) { + pixel = vec4(0.0, 0.0, 0.0, 1.0); + } + } + //White Cross + if (rfrag_tex_coord.x <= (cursor_pos.x + (1.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (0.0*pixelUnit.x))) { + if (rfrag_tex_coord.y <= (cursor_pos.y + (4.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (3.0*pixelUnit.y))) { + pixel = vec4(1.0, 1.0, 1.0, 1.0); + } + } + + if (rfrag_tex_coord.y <= (cursor_pos.y + (1.0*pixelUnit.y)) && + rfrag_tex_coord.y >= (cursor_pos.y - (0.0*pixelUnit.y))) { + if (rfrag_tex_coord.x <= (cursor_pos.x + (4.0*pixelUnit.x)) && + rfrag_tex_coord.x >= (cursor_pos.x - (3.0*pixelUnit.x))) { + pixel = vec4(1.0, 1.0, 1.0, 1.0); + } + } + } + color = vec4(pixel.rgb, 1.0); } diff --git a/src/video_core/host_shaders/vulkan_present.vert b/src/video_core/host_shaders/vulkan_present.vert index 2844fdafc..96ede7ec1 100644 --- a/src/video_core/host_shaders/vulkan_present.vert +++ b/src/video_core/host_shaders/vulkan_present.vert @@ -8,7 +8,7 @@ layout (location = 0) in vec2 vert_position; layout (location = 1) in vec2 vert_tex_coord; layout (location = 0) out vec2 frag_tex_coord; - +layout (location = 1) out vec2 pixelUnit; layout (push_constant, std140) uniform DrawInfo { mat4 modelview_matrix; vec4 i_resolution; @@ -24,4 +24,6 @@ void main() { vec4 position = vec4(vert_position, 0.0, 1.0) * modelview_matrix; gl_Position = vec4(position.x, position.y, 0.0, 1.0); frag_tex_coord = vert_tex_coord; + pixelUnit.x = 1/320.0; + pixelUnit.y = 1/240.0; } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 7a00ed342..4233eed5e 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -1020,7 +1020,8 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout& if (settings.shader_update_requested.exchange(false)) { ReloadPipeline(layout.render_3d_mode); } - + cursor_pos[0] += 1; + cursor_pos[1] += 1; PrepareDraw(frame, layout); const auto& top_screen = layout.top_screen; From 93cdb65693767c09af90b4da10e4c6a9cb4c3ce3 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Sun, 26 Apr 2026 11:21:20 -0700 Subject: [PATCH 06/12] expose inputs related to cursor from hid --- src/core/hle/service/hid/hid.cpp | 22 +++++++++++++++++++++- src/core/hle/service/hid/hid.h | 9 ++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 22fbec8cb..6204c1d5d 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -121,9 +121,15 @@ void Module::LoadInputDevices() { Settings::values.current_input_profile.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, buttons.begin(), Input::CreateDevice); + zl_button = Input::CreateDevice( + Settings::values.current_input_profile.buttons[Settings::NativeButton::ZL]); + zr_button = Input::CreateDevice( + Settings::values.current_input_profile.buttons[Settings::NativeButton::ZR]); circle_pad = Input::CreateDevice( Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CirclePad]); - motion_device = Input::CreateDevice( + c_stick = Input::CreateDevice( + Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CStick]); + motion_device = Input::CreateDevice( Settings::values.current_input_profile.motion_device); touch_device = Input::CreateDevice( Settings::values.current_input_profile.touch_device); @@ -217,6 +223,16 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); + + // Setting up inputs for Cursor Class. + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + stylusInput[0] = c_stick_x_f; + stylusInput[1] = c_stick_y_f; + stylusInput[2] = zl_button->GetStatus(); + stylusInput[3] = zr_button->GetStatus(); + // LOG_INFO(Service_HID, "C-Stick X: {}, C-Stick Y: {}, ZL: {}, ZR: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); + // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); @@ -320,6 +336,10 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { system.CoreTiming().ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } +std::array Module::getStylusInputs(){ + return stylusInput; +} + void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 33250e18b..7ef815838 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -242,7 +242,7 @@ public: class Interface : public ServiceFramework { public: Interface(std::shared_ptr hid, const char* name, u32 max_session); - + // Stylus X, Y, ZL, ZR std::shared_ptr GetModule() const; protected: @@ -335,7 +335,7 @@ public: void UseArticClient(const std::shared_ptr& client); void ReloadInputDevices(); - + std::array getStylusInputs(); const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -388,12 +388,15 @@ private: std::atomic is_device_reload_pending{true}; std::array, Settings::NativeButton::NUM_BUTTONS_HID> buttons; + std::unique_ptr zl_button; + std::unique_ptr zr_button; std::unique_ptr circle_pad; + std::unique_ptr c_stick; std::unique_ptr motion_device; std::unique_ptr controller_touch_device; std::unique_ptr touch_device; std::unique_ptr touch_btn_device; - + std::array stylusInput = {0}; std::shared_ptr artic_controller; std::shared_ptr artic_client; From bb5dbf4c0f1e5d124485b0d2ffed62688c065121 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Sun, 26 Apr 2026 14:18:14 -0700 Subject: [PATCH 07/12] setup cursor class and related variables/methods --- src/core/CMakeLists.txt | 2 + src/core/frontend/cursor.cpp | 270 +++++++++++++++++++++++++++++++ src/core/frontend/cursor.h | 50 ++++++ src/core/frontend/emu_window.cpp | 10 ++ src/core/frontend/emu_window.h | 3 + src/core/hle/service/hid/hid.cpp | 12 +- src/core/hle/service/hid/hid.h | 6 +- 7 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 src/core/frontend/cursor.cpp create mode 100644 src/core/frontend/cursor.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ec7a45bfe..499918581 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -121,6 +121,8 @@ add_library(citra_core STATIC frontend/camera/interface.h frontend/emu_window.cpp frontend/emu_window.h + frontend/cursor.cpp + frontend/cursor.h frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h frontend/image_interface.cpp diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp new file mode 100644 index 000000000..332f9d4c6 --- /dev/null +++ b/src/core/frontend/cursor.cpp @@ -0,0 +1,270 @@ +#include "cursor.h" +#include +#include +#include "common/logging/log.h" +#include "core\hle\service\hid\hid.h" + + +void Cursor::update(){ + if (emuWindow != nullptr){ + stylusInput = Service::HID::Module::getStylusInputs(); + modButtons = Service::HID::Module::getModButtons(); + if (deviceInUse == 0){ + if (inMacro){ + runMacro(); + } else { + // Reset the cursor position if macro was just played + if (justFinishedMacro > 0){ + justFinishedMacro = 0; + rawCursorPos[0] = macroInitPos[0]; + rawCursorPos[1] = macroInitPos[1]; + } + + // Macros + if (modButtons[0]){ + circle(0); + return; + } else if (modButtons[1]){ + rub(); + return; + } else if (modButtons[2]){ + if (!macroBtnPressed){ + // Add macro + return; + } + } else if (modButtons[3]){ + circle(1); + return; + } else { + if (macroBtnPressed){ + macroBtnPressed = false; + } + } + + normStylusDirection[0] = stylusInput[0]; + normStylusDirection[1] = stylusInput[1]; + + int maxSpeed = 50; + float multiplier = 0.5f * pow(4.0f, maxSpeed / 100.0f); // 0 is 0.5x speed, 100 is 2.0x speed. + float heightSpeed = (240.0f / 33.0f) * multiplier; + bool stylusModPressed = stylusInput[3]; + float responsecurve = 175.0f / 100.0f; + float speedupratio = 400.0f / 100.0f; + float joystickScaled[2] = {0.0f}; + float radialLength = std::sqrt((normStylusDirection[0] * normStylusDirection[0]) + (normStylusDirection[1] * normStylusDirection[1])); + float finalLength; + float curvedLength; + if (radialLength > 0) { + // Get X and Y as a relation to the radial length + float rComponents[2]; + rComponents[0] = normStylusDirection[0]/radialLength; + rComponents[1] = normStylusDirection[1]/radialLength; + // Apply response curve and output + curvedLength = std::pow(radialLength, responsecurve); + finalLength = stylusModPressed ? curvedLength * speedupratio : curvedLength; + joystickScaled[0] = rComponents[0] * finalLength; + joystickScaled[1] = rComponents[1] * finalLength; + } + // The code below sets the cursor position to the position of the joystick (absolute). Needs to be readjusted for standalone melonDS + // _joystickCursorPosition = vec2((NDS_SCREEN_WIDTH/2.0f)+(std::min(1.0,(normStylusDirection[0]/0.7071))*(NDS_SCREEN_WIDTH/2.0f)), (NDS_SCREEN_HEIGHT/2.0f)+(std::min(1.0,(normStylusDirection[1]/0.7071))*(NDS_SCREEN_HEIGHT/2.0f))); + + float tempX = joystickScaled[0]; + float tempY = joystickScaled[1]; + + switch (rotation) + { + case 1: // 90° + joystickScaled[0] = tempY; + joystickScaled[1] = -tempX; + break; + case 2: // 180° + joystickScaled[0] = -tempX; + joystickScaled[1] = -tempY; + break; + case 3: // 270° + joystickScaled[0] = -tempY; + joystickScaled[1] = tempX; + break; + default: + break; + } + + rawCursorPos[0] += joystickScaled[0]*heightSpeed; + rawCursorPos[1] += joystickScaled[1]*heightSpeed; + + // Clamp to region and ready position information for touchscreen + clamp(); + updateCursorPos(); + + // Handle stylus touch button presses + if (stylusInput[5]){ + touchScreen(); + wasTouching = true; + } else if (wasTouching && !stylusInput[5]){ + release(); + wasTouching = false; + } + } + } else { + //Update cursor based on mouse position + clamp(); + updateCursorPos(); + + // Handle stylus touch button presses + if (stylusInput[5]){ + touchScreen(); + wasTouching = true; + } else if (wasTouching && !stylusInput[5]){ + release(); + wasTouching = false; + } + + setDeviceInUse(0); + } + } +} + +void Cursor::setDeviceInUse(int device){ + deviceInUse = device; +} + +void Cursor::setRawCursorPos(float x, float y){ + rawCursorPos[0] = x; + rawCursorPos[1] = y; +} + +void Cursor::clamp(){ + rawCursorPos[0] = std::clamp(rawCursorPos[0], 0.0f, 319.0f); + rawCursorPos[1] = std::clamp(rawCursorPos[1], 0.0f, 239.0f); +} + +void Cursor::touchScreen(){ + emuWindow->TouchDirectlyPressed(cursorPos[0], cursorPos[1]); +} + +void Cursor::release(){ + emuWindow->TouchReleased(); +} +void Cursor::setEmuWindow(Frontend::EmuWindow* emuWindow){ + this->emuWindow = emuWindow; +} + +void Cursor::updateCursorPos(){ + cursorPos[0] = std::floor(rawCursorPos[0]); + cursorPos[1] = std::floor(rawCursorPos[1]); +} + +void Cursor::circle(int direction){ + macroBtnPressed = true; + inMacro = true; + wasTouching = true; + macroType = 1; + float radius = 240.0f/4.0f; + if (justFinishedMacro != 1){ // Set the original position if just starting + macroInitPos = {rawCursorPos[0], rawCursorPos[1]}; + } + + + std::vector> offsetArray; + if (direction == 0){ + offsetArray.push_back({(0.0f*radius), (-1.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (-0.7071f*radius)}); + offsetArray.push_back({(1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(0.0f*radius), (1.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(-1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (-0.7071f*radius)}); + } else { + offsetArray.push_back({(0.0f*radius), (-1.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (-0.7071f*radius)}); + offsetArray.push_back({(-1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(0.0f*radius), (1.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (-0.7071f*radius)}); + } + offsetArray = rotateVector(offsetArray); + + for (int i = 0; i < offsetArray.size(); i++){ + macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); + } + macroFrames = macroPositions.size(); + runMacro(); +} + +void Cursor::rub(){ + macroBtnPressed = true; + inMacro = true; + wasTouching = true; + macroType = 2; + float radius = 240.0f/6.0f; + if (justFinishedMacro != 2){ // Set the original position if just starting + macroInitPos = {rawCursorPos[0], rawCursorPos[1]}; + } + std::vector> offsetArray; + offsetArray.push_back({(0.0f*radius), 0}); + offsetArray.push_back({(0.5f*radius), 0}); + offsetArray.push_back({(1.0f*radius), 0}); + offsetArray.push_back({(0.5f*radius), 0}); + offsetArray.push_back({(0.0f*radius), 0}); + offsetArray.push_back({(-0.5f*radius), 0}); + offsetArray.push_back({(-1.0f*radius), 0}); + offsetArray.push_back({(-0.5f*radius), 0}); + offsetArray = rotateVector(offsetArray); + + for (int i = 0; i < offsetArray.size(); i++){ + macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); + } + macroFrames = macroPositions.size(); + runMacro(); +} + + +void Cursor::runMacro(){ + rawCursorPos[0] = macroPositions.front()[0]; + rawCursorPos[1] = macroPositions.front()[1]; + macroPositions.pop_front(); + clamp(); + updateCursorPos(); + touchScreen(); + macroFrames--; + if (macroFrames == 0){ + macroPositions.clear(); + inMacro = false; + justFinishedMacro = macroType; + } +} + +void Cursor::setRotation(int rot){ + rotation = rot; +} +void Cursor::setLayout(int lay){ + layout = lay; +} + +std::vector> Cursor::rotateVector(std::vector> input){ + for (auto& currArray : input){ + float tempX = currArray[0]; + float tempY = currArray[1]; + switch (rotation) + { + case 1: // 90° + currArray[0] = tempY; + currArray[1] = -tempX; + break; + case 2: // 180° + currArray[0] = -tempX; + currArray[1] = -tempY; + break; + case 3: // 270° + currArray[0] = -tempY; + currArray[1] = tempX; + break; + default: + break; + } + } + return input; +} diff --git a/src/core/frontend/cursor.h b/src/core/frontend/cursor.h new file mode 100644 index 000000000..5dd50b979 --- /dev/null +++ b/src/core/frontend/cursor.h @@ -0,0 +1,50 @@ +#ifndef CURSOR_H +#define CURSOR_H +#include +#include "emu_window.h" +#include +#include +namespace Frontend { + class EmuWindow; +} + +class Cursor +{ +public: + void update(); + void setRawCursorPos(float x, float y); + void setDeviceInUse(int device); + void setEmuWindow(Frontend::EmuWindow* emuWindow); + void setRotation(int rot); + void setLayout(int layout); + int cursorPos[2]; + float normStylusDirection[2]; + bool cursorEnabled = true; +private: + Frontend::EmuWindow* emuWindow = nullptr; + float rawCursorPos[2] = {159, 119}; + void touchScreen(); + void release(); + void clamp(); + void updateCursorPos(); + bool wasTouching; + std::array stylusInput; + std::array modButtons; + void circle(int direction); //0 is clockwise, 1 is counter clockwise + void rub(); + void runMacro(); + + std::vector> rotateVector(std::vector> input); + bool inMacro; + std::deque> macroPositions; + int macroFrames; + bool macroBtnPressed; + int macroType; + int justFinishedMacro; + std::array macroInitPos; + int rotation; + int layout; + int deviceInUse; //0 is Gamepad, 1 is Mouse/Tablet +}; + +#endif // CURSOR_H \ No newline at end of file diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 3216a1675..0ebfc46e8 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include "common/settings.h" @@ -191,6 +192,15 @@ bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { return true; } +bool EmuWindow::TouchDirectlyPressed(unsigned internal_x, unsigned internal_y) { + std::scoped_lock guard{touch_state->mutex}; + std::clamp(internal_x, 0, 319); + std::clamp(internal_y, 0, 239); + touch_state->touch_pressed = true; + touch_state->touch_x = internal_x; + touch_state->touch_y = internal_y; + return true; +} void EmuWindow::TouchReleased() { std::scoped_lock guard{touch_state->mutex}; touch_state->touch_pressed = false; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 175f41860..cd2f81b95 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -199,6 +199,9 @@ public: */ bool TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y); + /// Signal a touch pressed event occured, bypassing the framebuffer (e.g. stylus touch button pressed on controller) + bool TouchDirectlyPressed(unsigned internal_x, unsigned internal_y); + /// Signal that a touch released event has occurred (e.g. mouse click released) void TouchReleased(); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 6204c1d5d..5aedda619 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -24,11 +24,14 @@ #include "core/hle/service/ir/ir_user.h" #include "core/hle/service/service.h" #include "core/movie.h" +#include "hid.h" SERVICE_CONSTRUCT_IMPL(Service::HID::Module) SERIALIZE_EXPORT_IMPL(Service::HID::Module) namespace Service::HID { +std::array Module::stylusInput = {0}; +std::array Module::modButtons = {0}; template void Module::serialize(Archive& ar, const unsigned int file_version) { @@ -223,7 +226,6 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); - // Setting up inputs for Cursor Class. float c_stick_x_f, c_stick_y_f; std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); @@ -231,7 +233,9 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { stylusInput[1] = c_stick_y_f; stylusInput[2] = zl_button->GetStatus(); stylusInput[3] = zr_button->GetStatus(); - // LOG_INFO(Service_HID, "C-Stick X: {}, C-Stick Y: {}, ZL: {}, ZR: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); + for (int i = 0; i < 12; i++){ + modButtons[i] = buttons[i - BUTTON_HID_BEGIN]->GetStatus(); + } // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; @@ -340,6 +344,10 @@ std::array Module::getStylusInputs(){ return stylusInput; } +std::array Module::getModButtons(){ + return modButtons; +} + void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7ef815838..53ef85741 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -335,7 +335,8 @@ public: void UseArticClient(const std::shared_ptr& client); void ReloadInputDevices(); - std::array getStylusInputs(); + static std::array getStylusInputs(); + static std::array getModButtons(); const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -396,7 +397,8 @@ private: std::unique_ptr controller_touch_device; std::unique_ptr touch_device; std::unique_ptr touch_btn_device; - std::array stylusInput = {0}; + static std::array stylusInput; + static std::array modButtons; std::shared_ptr artic_controller; std::shared_ptr artic_client; From ae3bd188c0c0a5bf2802bf02c670080bd32680e8 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Tue, 28 Apr 2026 02:34:38 -0700 Subject: [PATCH 08/12] implemented functioning right stick cursor --- src/core/frontend/cursor.cpp | 24 +++++++++++-------- src/core/frontend/cursor.h | 6 ++--- src/core/frontend/emu_window.cpp | 5 ++-- src/core/hle/service/hid/hid.cpp | 15 ++++++------ src/core/hle/service/hid/hid.h | 4 ++-- .../host_shaders/opengl_present.frag | 2 +- .../host_shaders/vulkan_present.frag | 2 +- src/video_core/renderer_base.h | 3 +++ .../renderer_opengl/renderer_opengl.cpp | 9 +++---- .../renderer_opengl/renderer_opengl.h | 1 - .../renderer_vulkan/renderer_vulkan.cpp | 10 ++++---- .../renderer_vulkan/renderer_vulkan.h | 2 +- 12 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp index 332f9d4c6..8eac37830 100644 --- a/src/core/frontend/cursor.cpp +++ b/src/core/frontend/cursor.cpp @@ -2,6 +2,7 @@ #include #include #include "common/logging/log.h" +#include "common/logging/types.h" #include "core\hle\service\hid\hid.h" @@ -9,10 +10,12 @@ void Cursor::update(){ if (emuWindow != nullptr){ stylusInput = Service::HID::Module::getStylusInputs(); modButtons = Service::HID::Module::getModButtons(); + setRotation(); if (deviceInUse == 0){ if (inMacro){ runMacro(); } else { + // LOG_INFO(Core, "Stylus X: {:.2f}, Stylus Y: {:.2f}, Stylus Mod: {}, Stylus Touch: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); // Reset the cursor position if macro was just played if (justFinishedMacro > 0){ justFinishedMacro = 0; @@ -47,7 +50,7 @@ void Cursor::update(){ int maxSpeed = 50; float multiplier = 0.5f * pow(4.0f, maxSpeed / 100.0f); // 0 is 0.5x speed, 100 is 2.0x speed. float heightSpeed = (240.0f / 33.0f) * multiplier; - bool stylusModPressed = stylusInput[3]; + bool stylusModPressed = stylusInput[2]; float responsecurve = 175.0f / 100.0f; float speedupratio = 400.0f / 100.0f; float joystickScaled[2] = {0.0f}; @@ -97,10 +100,10 @@ void Cursor::update(){ updateCursorPos(); // Handle stylus touch button presses - if (stylusInput[5]){ + if (stylusInput[3]){ touchScreen(); wasTouching = true; - } else if (wasTouching && !stylusInput[5]){ + } else if (wasTouching && !stylusInput[3]){ release(); wasTouching = false; } @@ -111,10 +114,10 @@ void Cursor::update(){ updateCursorPos(); // Handle stylus touch button presses - if (stylusInput[5]){ + if (stylusInput[3]){ touchScreen(); wasTouching = true; - } else if (wasTouching && !stylusInput[5]){ + } else if (wasTouching && !stylusInput[3]){ release(); wasTouching = false; } @@ -237,11 +240,12 @@ void Cursor::runMacro(){ } } -void Cursor::setRotation(int rot){ - rotation = rot; -} -void Cursor::setLayout(int lay){ - layout = lay; +void Cursor::setRotation(){ + if (emuWindow->GetFramebufferLayout().is_portrait){ + rotation = 3; + } else { + rotation = 0; + } } std::vector> Cursor::rotateVector(std::vector> input){ diff --git a/src/core/frontend/cursor.h b/src/core/frontend/cursor.h index 5dd50b979..6147944e3 100644 --- a/src/core/frontend/cursor.h +++ b/src/core/frontend/cursor.h @@ -15,8 +15,7 @@ public: void setRawCursorPos(float x, float y); void setDeviceInUse(int device); void setEmuWindow(Frontend::EmuWindow* emuWindow); - void setRotation(int rot); - void setLayout(int layout); + void setRotation(); int cursorPos[2]; float normStylusDirection[2]; bool cursorEnabled = true; @@ -29,7 +28,7 @@ private: void updateCursorPos(); bool wasTouching; std::array stylusInput; - std::array modButtons; + std::array modButtons; void circle(int direction); //0 is clockwise, 1 is counter clockwise void rub(); void runMacro(); @@ -43,7 +42,6 @@ private: int justFinishedMacro; std::array macroInitPos; int rotation; - int layout; int deviceInUse; //0 is Gamepad, 1 is Mouse/Tablet }; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 0ebfc46e8..5e8266ef5 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -193,12 +193,13 @@ bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { } bool EmuWindow::TouchDirectlyPressed(unsigned internal_x, unsigned internal_y) { + //This assumes 0, 0 is bottom left corner std::scoped_lock guard{touch_state->mutex}; std::clamp(internal_x, 0, 319); std::clamp(internal_y, 0, 239); touch_state->touch_pressed = true; - touch_state->touch_x = internal_x; - touch_state->touch_y = internal_y; + touch_state->touch_x = internal_x/319.0f; + touch_state->touch_y = (239-internal_y)/239.0f; return true; } void EmuWindow::TouchReleased() { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 5aedda619..74f5258d1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -30,8 +30,8 @@ SERVICE_CONSTRUCT_IMPL(Service::HID::Module) SERIALIZE_EXPORT_IMPL(Service::HID::Module) namespace Service::HID { -std::array Module::stylusInput = {0}; -std::array Module::modButtons = {0}; +std::array Module::stylusInput = {}; +std::array Module::modButtons = {}; template void Module::serialize(Archive& ar, const unsigned int file_version) { @@ -231,12 +231,11 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); stylusInput[0] = c_stick_x_f; stylusInput[1] = c_stick_y_f; - stylusInput[2] = zl_button->GetStatus(); - stylusInput[3] = zr_button->GetStatus(); - for (int i = 0; i < 12; i++){ - modButtons[i] = buttons[i - BUTTON_HID_BEGIN]->GetStatus(); + stylusInput[2] = static_cast(zl_button->GetStatus()); + stylusInput[3] = static_cast(zr_button->GetStatus()); + for (int i = 0; i < 4; i++){ + modButtons[i] = zl_button->GetStatus() && buttons[i - BUTTON_HID_BEGIN]->GetStatus(); } - // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); @@ -344,7 +343,7 @@ std::array Module::getStylusInputs(){ return stylusInput; } -std::array Module::getModButtons(){ +std::array Module::getModButtons(){ return modButtons; } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 53ef85741..363bb0350 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -336,7 +336,7 @@ public: void ReloadInputDevices(); static std::array getStylusInputs(); - static std::array getModButtons(); + static std::array getModButtons(); const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -398,7 +398,7 @@ private: std::unique_ptr touch_device; std::unique_ptr touch_btn_device; static std::array stylusInput; - static std::array modButtons; + static std::array modButtons; std::shared_ptr artic_controller; std::shared_ptr artic_client; diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag index ef8610a34..fa996d37b 100644 --- a/src/video_core/host_shaders/opengl_present.frag +++ b/src/video_core/host_shaders/opengl_present.frag @@ -17,7 +17,7 @@ uniform int layer; in vec2 pixelUnit; void main() { vec4 pixel = texture(color_texture, frag_tex_coord); - vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, 1.0 - frag_tex_coord.x); + vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, frag_tex_coord.x); //Cursor if (cursor_enable){ //Black Outline diff --git a/src/video_core/host_shaders/vulkan_present.frag b/src/video_core/host_shaders/vulkan_present.frag index 54d61ea8d..2e412e5ce 100644 --- a/src/video_core/host_shaders/vulkan_present.frag +++ b/src/video_core/host_shaders/vulkan_present.frag @@ -40,7 +40,7 @@ vec4 GetScreen(int screen_id) { void main() { vec4 pixel = GetScreen(screen_id_l); - vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, 1.0 - frag_tex_coord.x); + vec2 rfrag_tex_coord = vec2(frag_tex_coord.y, frag_tex_coord.x); //Cursor if (cursor_enable == 1){ //Black Outline diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 5a737a37f..dcea9d62e 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -7,6 +7,8 @@ #include "common/common_types.h" #include "core/frontend/framebuffer_layout.h" #include "video_core/rasterizer_interface.h" +#include "core\frontend\cursor.h" +class Cursor; namespace Frontend { class EmuWindow; @@ -109,6 +111,7 @@ protected: RendererSettings settings; Frontend::EmuWindow& render_window; /// Reference to the render window handle. Frontend::EmuWindow* secondary_window; /// Reference to the secondary render window handle. + Cursor* vCursor; protected: f32 current_fps = 0.0f; /// Current framerate, should be set by the renderer diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index d0eaad708..324792f18 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -78,6 +78,8 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Pica::PicaCore& pica_, rasterizer{system.Memory(), pica, system.CustomTexManager(), *this, driver}, frame_dumper{system, window} { const bool has_debug_tool = driver.HasDebugTool(); + vCursor = new Cursor(); + vCursor->setEmuWindow(&window); window.mailbox = std::make_unique(has_debug_tool); if (secondary_window) { secondary_window->mailbox = std::make_unique(has_debug_tool); @@ -235,6 +237,7 @@ void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout, state.draw.draw_framebuffer = frame->render.handle; state.Apply(); + vCursor->update(); DrawScreens(layout, flipped); // Create a fence for the frontend to wait on and swap this frame to OffTex frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); @@ -568,7 +571,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl } else { glUniform1i(uniform_cursor_enable, 0); } - glUniform2f(uniform_cursor_pos, cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); + glUniform2f(uniform_cursor_pos, vCursor->cursorPos[0]/320.0f, vCursor->cursorPos[1]/240.0f); state.texture_units[0].texture_2d = screen_info.display_texture; state.texture_units[0].sampler = sampler; state.Apply(); @@ -645,7 +648,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l, } else { glUniform1i(uniform_cursor_enable, 0); } - glUniform2f(uniform_cursor_pos, cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); + glUniform2f(uniform_cursor_pos, vCursor->cursorPos[0]/320.0f, vCursor->cursorPos[1]/240.0f); state.texture_units[0].texture_2d = screen_info_l.display_texture; state.texture_units[1].texture_2d = screen_info_r.display_texture; state.texture_units[0].sampler = sampler; @@ -706,8 +709,6 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f } glUniform1i(uniform_layer, 0); - cursor_pos[0] += 1; - cursor_pos[1] += 1; if (!Settings::values.swap_screen.GetValue()) { DrawTopScreen(layout, top_screen); glUniform1i(uniform_layer, 0); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index a6c56072b..68f0c260d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -114,7 +114,6 @@ private: GLuint attrib_position; GLuint attrib_tex_coord; int currScreenDraw; // 0 is Top, 1 is Bottom - std::array cursor_pos = {0,0}; FrameDumperOpenGL frame_dumper; }; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 4233eed5e..85b1616cd 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -127,6 +127,8 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_, update_queue, main_present_window.ImageCount()}, present_heap{instance, scheduler.GetMasterSemaphore(), PRESENT_BINDINGS, 32} { + vCursor = new Cursor(); + vCursor->setEmuWindow(&window); CompileShaders(); BuildLayouts(); BuildPipelines(); @@ -249,7 +251,7 @@ void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::Framebu clear_color.float32[1] = Settings::values.bg_green.GetValue(); clear_color.float32[2] = Settings::values.bg_blue.GetValue(); clear_color.float32[3] = 1.0f; - + vCursor->update(); DrawScreens(frame, layout, flipped); scheduler.Flush(frame->render_ready); @@ -793,7 +795,7 @@ void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w, } else { draw_info.cursor_enable = 0; } - draw_info.cursor_pos = Common::MakeVec(cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); + draw_info.cursor_pos = Common::MakeVec(vCursor->cursorPos[0]/320.0f, vCursor->cursorPos[1]/240.0f); draw_info.screen_id_l = screen_id; scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) { @@ -871,7 +873,7 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl } else { draw_info.cursor_enable = 0; } - draw_info.cursor_pos = Common::MakeVec(cursor_pos[0]/320.0f, cursor_pos[1]/240.0f); + draw_info.cursor_pos = Common::MakeVec(vCursor->cursorPos[0]/320.0f, vCursor->cursorPos[1]/240.0f); draw_info.screen_id_l = screen_id_l; draw_info.screen_id_r = screen_id_r; @@ -1020,8 +1022,6 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout& if (settings.shader_update_requested.exchange(false)) { ReloadPipeline(layout.render_3d_mode); } - cursor_pos[0] += 1; - cursor_pos[1] += 1; PrepareDraw(frame, layout); const auto& top_screen = layout.top_screen; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 481d76187..a5241df6b 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -6,6 +6,7 @@ #include "common/common_types.h" #include "common/math_util.h" +#include "core/frontend/cursor.h" #include "video_core/renderer_base.h" #ifdef HAVE_LIBRETRO #include "citra_libretro/libretro_vk.h" @@ -154,7 +155,6 @@ private: vk::Pipeline cursor_pipeline{}; vk::UniquePipelineLayout cursor_pipeline_layout{}; int currScreenDraw; // 0 is Top, 1 is Bottom - std::array cursor_pos = {0,0}; }; } // namespace Vulkan From 0ba8e18dc4c9816d077ff3108d58453746884bce Mon Sep 17 00:00:00 2001 From: KojoZero Date: Tue, 28 Apr 2026 20:13:55 -0700 Subject: [PATCH 09/12] added c-stick toggle and stylus related gui changes --- .../configuration/configure_input.cpp | 2 +- src/citra_qt/configuration/configure_input.ui | 153 +++++++++------- src/common/settings.h | 3 + src/core/frontend/cursor.cpp | 172 +++++++++--------- src/core/hle/service/hid/hid.cpp | 12 +- src/core/hle/service/hid/hid.h | 4 +- src/core/hle/service/ir/extra_hid.cpp | 19 +- 7 files changed, 208 insertions(+), 157 deletions(-) diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 2cc319968..67b697dae 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -171,7 +171,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) ui->buttonDpadUp, ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR, ui->buttonStart, ui->buttonSelect, ui->buttonDebug, ui->buttonGpio14, ui->buttonZL, ui->buttonZR, - ui->buttonHome, ui->buttonPower, + ui->buttonHome, ui->buttonPower, ui->buttonToggleCStick, }; analog_map_buttons = {{ diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 67cc7688f..bcc6820a2 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -29,7 +29,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -68,6 +68,14 @@ true + + + 0 + -180 + 416 + 802 + + @@ -88,7 +96,7 @@ - ZR: + Stylus Touch / ZR: @@ -124,7 +132,7 @@ - ZL: + Stylus Mod / ZL: @@ -344,17 +352,17 @@ false - - + + - + - Start: + Circle Mod: - + @@ -380,24 +388,6 @@ - - - - - - Home: - - - - - - - - - - - - @@ -416,24 +406,6 @@ - - - - - - Circle Mod: - - - - - - - - - - - - @@ -452,6 +424,42 @@ + + + + + + Start: + + + + + + + + + + + + + + + + + + Home: + + + + + + + + + + + + @@ -470,6 +478,24 @@ + + + + + + Toggle C-Stick: + + + + + + + + + + + + @@ -572,7 +598,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint @@ -580,7 +606,7 @@ Deadzone: 0 - Qt::AlignHCenter + Qt::AlignmentFlag::AlignHCenter @@ -589,7 +615,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -634,25 +660,25 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Diagonals - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter - Qt::Horizontal + Qt::Orientation::Horizontal @@ -697,7 +723,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -707,7 +733,7 @@ - C-Stick + Stylus / C-Stick false @@ -868,14 +894,14 @@ Diagonals - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter - Qt::Horizontal + Qt::Orientation::Horizontal @@ -884,7 +910,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint @@ -894,7 +920,7 @@ Deadzone: 0 - Qt::AlignHCenter + Qt::AlignmentFlag::AlignHCenter @@ -903,7 +929,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -919,7 +945,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -957,7 +983,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Motion / Touch... @@ -985,7 +1011,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Auto Map @@ -1013,7 +1039,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Clear All @@ -1041,7 +1067,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Restore Defaults @@ -1094,6 +1120,7 @@ buttonCircleMod buttonDebug buttonGpio14 + buttonToggleCStick buttonMotionTouch buttonAutoMap buttonClearAll diff --git a/src/common/settings.h b/src/common/settings.h index 5eca53421..f67f072df 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -151,6 +151,8 @@ enum Values { Home, Power, + ToggleCStick, + NumButtons, }; @@ -185,6 +187,7 @@ static const std::array mapping = {{ "button_zr", "button_home", "button_power", + "button_togglecstick", }}; } // namespace NativeButton diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp index 8eac37830..93d41058a 100644 --- a/src/core/frontend/cursor.cpp +++ b/src/core/frontend/cursor.cpp @@ -12,100 +12,102 @@ void Cursor::update(){ modButtons = Service::HID::Module::getModButtons(); setRotation(); if (deviceInUse == 0){ - if (inMacro){ - runMacro(); - } else { - // LOG_INFO(Core, "Stylus X: {:.2f}, Stylus Y: {:.2f}, Stylus Mod: {}, Stylus Touch: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); - // Reset the cursor position if macro was just played - if (justFinishedMacro > 0){ - justFinishedMacro = 0; - rawCursorPos[0] = macroInitPos[0]; - rawCursorPos[1] = macroInitPos[1]; - } - - // Macros - if (modButtons[0]){ - circle(0); - return; - } else if (modButtons[1]){ - rub(); - return; - } else if (modButtons[2]){ - if (!macroBtnPressed){ - // Add macro - return; - } - } else if (modButtons[3]){ - circle(1); - return; + if (!Service::HID::Module::cstickEnabled){ + if (inMacro){ + runMacro(); } else { - if (macroBtnPressed){ - macroBtnPressed = false; + // LOG_INFO(Core, "Stylus X: {:.2f}, Stylus Y: {:.2f}, Stylus Mod: {}, Stylus Touch: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); + // Reset the cursor position if macro was just played + if (justFinishedMacro > 0){ + justFinishedMacro = 0; + rawCursorPos[0] = macroInitPos[0]; + rawCursorPos[1] = macroInitPos[1]; } - } - normStylusDirection[0] = stylusInput[0]; - normStylusDirection[1] = stylusInput[1]; + // Macros + if (modButtons[0]){ + circle(0); + return; + } else if (modButtons[1]){ + rub(); + return; + } else if (modButtons[2]){ + if (!macroBtnPressed){ + // Add macro + return; + } + } else if (modButtons[3]){ + circle(1); + return; + } else { + if (macroBtnPressed){ + macroBtnPressed = false; + } + } - int maxSpeed = 50; - float multiplier = 0.5f * pow(4.0f, maxSpeed / 100.0f); // 0 is 0.5x speed, 100 is 2.0x speed. - float heightSpeed = (240.0f / 33.0f) * multiplier; - bool stylusModPressed = stylusInput[2]; - float responsecurve = 175.0f / 100.0f; - float speedupratio = 400.0f / 100.0f; - float joystickScaled[2] = {0.0f}; - float radialLength = std::sqrt((normStylusDirection[0] * normStylusDirection[0]) + (normStylusDirection[1] * normStylusDirection[1])); - float finalLength; - float curvedLength; - if (radialLength > 0) { - // Get X and Y as a relation to the radial length - float rComponents[2]; - rComponents[0] = normStylusDirection[0]/radialLength; - rComponents[1] = normStylusDirection[1]/radialLength; - // Apply response curve and output - curvedLength = std::pow(radialLength, responsecurve); - finalLength = stylusModPressed ? curvedLength * speedupratio : curvedLength; - joystickScaled[0] = rComponents[0] * finalLength; - joystickScaled[1] = rComponents[1] * finalLength; - } - // The code below sets the cursor position to the position of the joystick (absolute). Needs to be readjusted for standalone melonDS - // _joystickCursorPosition = vec2((NDS_SCREEN_WIDTH/2.0f)+(std::min(1.0,(normStylusDirection[0]/0.7071))*(NDS_SCREEN_WIDTH/2.0f)), (NDS_SCREEN_HEIGHT/2.0f)+(std::min(1.0,(normStylusDirection[1]/0.7071))*(NDS_SCREEN_HEIGHT/2.0f))); + normStylusDirection[0] = stylusInput[0]; + normStylusDirection[1] = stylusInput[1]; - float tempX = joystickScaled[0]; - float tempY = joystickScaled[1]; + int maxSpeed = 50; + float multiplier = 0.5f * pow(4.0f, maxSpeed / 100.0f); // 0 is 0.5x speed, 100 is 2.0x speed. + float heightSpeed = (240.0f / 33.0f) * multiplier; + bool stylusModPressed = stylusInput[2]; + float responsecurve = 175.0f / 100.0f; + float speedupratio = 400.0f / 100.0f; + float joystickScaled[2] = {0.0f}; + float radialLength = std::sqrt((normStylusDirection[0] * normStylusDirection[0]) + (normStylusDirection[1] * normStylusDirection[1])); + float finalLength; + float curvedLength; + if (radialLength > 0) { + // Get X and Y as a relation to the radial length + float rComponents[2]; + rComponents[0] = normStylusDirection[0]/radialLength; + rComponents[1] = normStylusDirection[1]/radialLength; + // Apply response curve and output + curvedLength = std::pow(radialLength, responsecurve); + finalLength = stylusModPressed ? curvedLength * speedupratio : curvedLength; + joystickScaled[0] = rComponents[0] * finalLength; + joystickScaled[1] = rComponents[1] * finalLength; + } + // The code below sets the cursor position to the position of the joystick (absolute). Needs to be readjusted for standalone melonDS + // _joystickCursorPosition = vec2((NDS_SCREEN_WIDTH/2.0f)+(std::min(1.0,(normStylusDirection[0]/0.7071))*(NDS_SCREEN_WIDTH/2.0f)), (NDS_SCREEN_HEIGHT/2.0f)+(std::min(1.0,(normStylusDirection[1]/0.7071))*(NDS_SCREEN_HEIGHT/2.0f))); - switch (rotation) - { - case 1: // 90° - joystickScaled[0] = tempY; - joystickScaled[1] = -tempX; - break; - case 2: // 180° - joystickScaled[0] = -tempX; - joystickScaled[1] = -tempY; - break; - case 3: // 270° - joystickScaled[0] = -tempY; - joystickScaled[1] = tempX; - break; - default: - break; - } + float tempX = joystickScaled[0]; + float tempY = joystickScaled[1]; - rawCursorPos[0] += joystickScaled[0]*heightSpeed; - rawCursorPos[1] += joystickScaled[1]*heightSpeed; + switch (rotation) + { + case 1: // 90° + joystickScaled[0] = tempY; + joystickScaled[1] = -tempX; + break; + case 2: // 180° + joystickScaled[0] = -tempX; + joystickScaled[1] = -tempY; + break; + case 3: // 270° + joystickScaled[0] = -tempY; + joystickScaled[1] = tempX; + break; + default: + break; + } - // Clamp to region and ready position information for touchscreen - clamp(); - updateCursorPos(); + rawCursorPos[0] += joystickScaled[0]*heightSpeed; + rawCursorPos[1] += joystickScaled[1]*heightSpeed; - // Handle stylus touch button presses - if (stylusInput[3]){ - touchScreen(); - wasTouching = true; - } else if (wasTouching && !stylusInput[3]){ - release(); - wasTouching = false; + // Clamp to region and ready position information for touchscreen + clamp(); + updateCursorPos(); + + // Handle stylus touch button presses + if (stylusInput[3]){ + touchScreen(); + wasTouching = true; + } else if (wasTouching && !stylusInput[3]){ + release(); + wasTouching = false; + } } } } else { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 74f5258d1..77b51c922 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -32,7 +32,7 @@ SERIALIZE_EXPORT_IMPL(Service::HID::Module) namespace Service::HID { std::array Module::stylusInput = {}; std::array Module::modButtons = {}; - +bool Module::cstickEnabled = false; template void Module::serialize(Archive& ar, const unsigned int file_version) { DEBUG_SERIALIZATION_POINT; @@ -128,6 +128,8 @@ void Module::LoadInputDevices() { Settings::values.current_input_profile.buttons[Settings::NativeButton::ZL]); zr_button = Input::CreateDevice( Settings::values.current_input_profile.buttons[Settings::NativeButton::ZR]); + toggle_cstick_button = Input::CreateDevice( + Settings::values.current_input_profile.buttons[Settings::NativeButton::ToggleCStick]); circle_pad = Input::CreateDevice( Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CirclePad]); c_stick = Input::CreateDevice( @@ -236,6 +238,14 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { for (int i = 0; i < 4; i++){ modButtons[i] = zl_button->GetStatus() && buttons[i - BUTTON_HID_BEGIN]->GetStatus(); } + + //Toggle for Cstick + if (!prev_toggle_cstick_button_state && toggle_cstick_button->GetStatus()){ + cstickEnabled = !cstickEnabled; + } + //LOG_INFO(Service_HID, "C-Stick Enabled: {}, Prev C-Stick Button State {}, Current C-Stick Button State {}", cstickEnabled, prev_toggle_cstick_button_state, toggle_cstick_button->GetStatus()); + prev_toggle_cstick_button_state = toggle_cstick_button->GetStatus(); + // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 363bb0350..5fcf82642 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -244,7 +244,6 @@ public: Interface(std::shared_ptr hid, const char* name, u32 max_session); // Stylus X, Y, ZL, ZR std::shared_ptr GetModule() const; - protected: /** * HID::GetIPCHandles service function @@ -337,6 +336,7 @@ public: void ReloadInputDevices(); static std::array getStylusInputs(); static std::array getModButtons(); + static bool cstickEnabled; const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -391,6 +391,7 @@ private: buttons; std::unique_ptr zl_button; std::unique_ptr zr_button; + std::unique_ptr toggle_cstick_button; std::unique_ptr circle_pad; std::unique_ptr c_stick; std::unique_ptr motion_device; @@ -399,6 +400,7 @@ private: std::unique_ptr touch_btn_device; static std::array stylusInput; static std::array modButtons; + bool prev_toggle_cstick_button_state = false; std::shared_ptr artic_controller; std::shared_ptr artic_client; diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index e5dc486cb..fcb1b7dab 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -262,13 +262,20 @@ void ExtraHID::SendHIDStatus() { } else { float x, y; std::tie(x, y) = c_stick->GetStatus(); - - response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); - response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); - response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); response.buttons.battery_level.Assign(0x1F); - response.buttons.zl_not_held.Assign(!zl->GetStatus()); - response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + if (Service::HID::Module::cstickEnabled){ + response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + } else { + response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER)); + response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER)); + response.buttons.zl_not_held.Assign(true); + response.buttons.zr_not_held.Assign(true); + } + response.buttons.r_not_held.Assign(1); response.unknown = 0; } From 9149f29c1a1f8bf5b356bfee4cfd1be9e046b3f1 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Wed, 29 Apr 2026 22:36:35 -0700 Subject: [PATCH 10/12] updated TouchDirectlyPressed, fixed auto map, and cursor rotation --- src/citra_qt/configuration/config.cpp | 1 + src/core/frontend/cursor.cpp | 2 +- src/core/frontend/emu_window.cpp | 4 ++-- src/core/frontend/framebuffer_layout.cpp | 1 + src/input_common/sdl/sdl_impl.cpp | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index a5aa9896b..b7a2b6693 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -33,6 +33,7 @@ const std::array QtConfig::default_butt Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_O, Qt::Key_P, Qt::Key_1, Qt::Key_2, Qt::Key_B, Qt::Key_V, + Qt::Key_U, }; const std::array, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{ diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp index 93d41058a..a6626b076 100644 --- a/src/core/frontend/cursor.cpp +++ b/src/core/frontend/cursor.cpp @@ -244,7 +244,7 @@ void Cursor::runMacro(){ void Cursor::setRotation(){ if (emuWindow->GetFramebufferLayout().is_portrait){ - rotation = 3; + rotation = 1; } else { rotation = 0; } diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 5e8266ef5..e56fe7ba4 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -198,8 +198,8 @@ bool EmuWindow::TouchDirectlyPressed(unsigned internal_x, unsigned internal_y) { std::clamp(internal_x, 0, 319); std::clamp(internal_y, 0, 239); touch_state->touch_pressed = true; - touch_state->touch_x = internal_x/319.0f; - touch_state->touch_y = (239-internal_y)/239.0f; + touch_state->touch_x = internal_x/320.0f; + touch_state->touch_y = (239-internal_y)/240.0f; return true; } void EmuWindow::TouchReleased() { diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 918c1454f..c0eb2fcb8 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -639,6 +639,7 @@ FramebufferLayout reverseLayout(FramebufferLayout layout) { layout.additional_screen.top = layout.height - oldRight; layout.additional_screen.bottom = layout.height - oldLeft; } + layout.is_portrait = true; return layout; } diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 71f2be379..7a22d4e4c 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -140,6 +140,7 @@ constexpr std::array Date: Wed, 13 May 2026 23:11:30 -0700 Subject: [PATCH 11/12] attempted build fix 1 --- src/core/frontend/cursor.cpp | 4 ++-- src/video_core/renderer_base.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp index a6626b076..800a95aa5 100644 --- a/src/core/frontend/cursor.cpp +++ b/src/core/frontend/cursor.cpp @@ -192,7 +192,7 @@ void Cursor::circle(int direction){ } offsetArray = rotateVector(offsetArray); - for (int i = 0; i < offsetArray.size(); i++){ + for (std::size_t i = 0; i < offsetArray.size(); i++){ macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); } macroFrames = macroPositions.size(); @@ -219,7 +219,7 @@ void Cursor::rub(){ offsetArray.push_back({(-0.5f*radius), 0}); offsetArray = rotateVector(offsetArray); - for (int i = 0; i < offsetArray.size(); i++){ + for (std::size_t i = 0; i < offsetArray.size(); i++){ macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); } macroFrames = macroPositions.size(); diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index dcea9d62e..47f18b4cc 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -7,7 +7,7 @@ #include "common/common_types.h" #include "core/frontend/framebuffer_layout.h" #include "video_core/rasterizer_interface.h" -#include "core\frontend\cursor.h" +#include "core/frontend/cursor.h" class Cursor; namespace Frontend { From 36118a14093c61c131e9ec049ebeb56da19917a1 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Wed, 13 May 2026 23:23:33 -0700 Subject: [PATCH 12/12] attempted build fix 2 --- src/core/frontend/cursor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp index 800a95aa5..e85497a32 100644 --- a/src/core/frontend/cursor.cpp +++ b/src/core/frontend/cursor.cpp @@ -3,7 +3,7 @@ #include #include "common/logging/log.h" #include "common/logging/types.h" -#include "core\hle\service\hid\hid.h" +#include "core/hle/service/hid/hid.h" void Cursor::update(){