gfx_widgets+d3d: animate task-message hourglass on all drivers

The hourglass icon on the task-progress notification widget never
visibly rotated.  The animation system was correctly easing
msg->hourglass_rotation through 0 -> -2*pi every six seconds, but
the rendered icon stayed static across every backend.

Three independent bugs, each blocking a different family of
drivers:

  1. gfx/gfx_widgets.c - the call site that draws the hourglass
     forwarded the live `radians` value to gfx_widgets_draw_icon
     but left `cosine`/`sine` at their cos(0)/sin(0) defaults.
     The matrix-rotation path (gl, gl1, glcore, vulkan, d3d8)
     consumes those trig values via gfx_display_rotate_z, so it
     was always building an identity rotation regardless of
     `radians`.

     Fix: recompute cosf(radians)/sinf(radians) alongside `radians`
     in the in-progress branch.

  2. gfx/drivers/d3d_shaders/sprite_sm4.hlsl.h - the sprite shader
     used by the d3d10 / d3d11 / d3d12 menu display drivers.  The
     C code on those drivers dutifully writes
     `sprite->params.rotation = draw->rotation` into the vertex
     stream, but the shader pipeline never reads it: the GSInput
     struct doesn't have a `params` field, so rotation is dropped
     at the VS->GS boundary, and the geometry shader builds the
     four corner vertices straight from `position.xy + position.zw
     * cornerOffset` with no rotation applied.

     Fix: forward `params` from VS to GS (params.x is already
     consumed by the VS for scaling; we need params.y in the GS
     for rotation), and rewrite the GS to compute corner offsets
     by rotating per-corner pixel-space sign vectors `(sx, sy) in
     {+/-1}^2` and scaling them back by `position.z * 0.5` (X) and
     `position.w * 0.5` (Y).  Because position.z = 2W/vw and
     position.w = 2W/vh both encode the same pixel-space length W
     for a square icon, this preserves the icon's shape under
     rotation on a non-square viewport.  Non-square icons would
     skew, but the only caller of rotation today (the hourglass)
     is square.

  3. gfx/drivers/d3d9hlsl.c, gfx/drivers/d3d9cg.c - the d3d9
     menu display drivers' single-sprite path (no explicit vertex
     array supplied by caller, which is what gfx_widgets_draw_icon
     does) builds an axis-aligned quad from draw->x / draw->y /
     draw->width / draw->height directly, applies scale_factor,
     and never references draw->rotation or draw->matrix_data at
     all - so rotation was silently dropped on both shader
     paths.

     Fix: when draw->rotation is non-zero, rotate each of the
     four pixel-space corner offsets around the quad centre, then
     normalise back to [0,1] via 1/video_width and 1/video_height.
     Same aspect-correct approach as the d3d_shaders fix above.
     Add <math.h> to d3d9hlsl.c (d3d9cg.c already had it).

The d3d9 multi-vertex path uses the matrix_data route, so it's
handled by fix (1) alone and needs no driver-side change.

Tested on gl / gl1 / glcore / vulkan / d3d9_cg / d3d9_hlsl /
d3d10 / d3d11 / d3d12.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-28 18:13:35 +02:00
1 parent 832250be0a
commit ace9c9f2f3
4 files changed
+180 -35

No files matched your search

+54 -8
View File
@@ -870,17 +870,63 @@ static void gfx_display_d3d9_cg_draw(gfx_display_ctx_draw_t *draw,
y2 = cy + hh;
}
quad[0].x = x1; quad[0].y = y1; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
/* Apply draw->rotation around the quad center. Rotation is
* computed in pixel space (using draw->width / draw->height as
* the icon's true square extents) and then converted back to
* normalised [0,1], so a non-square viewport does not skew the
* rotated icon. */
if (draw->rotation != 0.0f)
{
float cx = (x1 + x2) * 0.5f;
float cy = (y1 + y2) * 0.5f;
float half_w = draw->width * 0.5f;
float half_h = draw->height * 0.5f;
if (draw->scale_factor && draw->scale_factor != 1.0f)
{
half_w *= draw->scale_factor;
half_h *= draw->scale_factor;
}
{
float c = cosf(draw->rotation);
float s = sinf(draw->rotation);
float inv_vw = 1.0f / (float)video_width;
float inv_vh = 1.0f / (float)video_height;
float ox[4] = { -half_w, half_w, -half_w, half_w };
float oy[4] = { -half_h, -half_h, half_h, half_h };
float rx[4], ry[4];
int k;
for (k = 0; k < 4; k++)
{
rx[k] = (ox[k] * c - oy[k] * s) * inv_vw;
ry[k] = (ox[k] * s + oy[k] * c) * inv_vh;
}
quad[0].x = cx + rx[0]; quad[0].y = cy + ry[0]; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
quad[1].x = x2; quad[1].y = y1; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[1].x = cx + rx[1]; quad[1].y = cy + ry[1]; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[2].x = x1; quad[2].y = y2; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[2].x = cx + rx[2]; quad[2].y = cy + ry[2]; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[3].x = x2; quad[3].y = y2; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
quad[3].x = cx + rx[3]; quad[3].y = cy + ry[3]; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
}
}
else
{
quad[0].x = x1; quad[0].y = y1; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
quad[1].x = x2; quad[1].y = y1; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[2].x = x1; quad[2].y = y2; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[3].x = x2; quad[3].y = y2; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
}
/* Top-down ortho: maps X [0,1]->[-1,1], Y [0,1]->[1,-1] (Y=0 at top).
* Column-major (transposed) layout for the Cg stock shader which
+58 -8
View File
@@ -42,6 +42,7 @@
#include <string.h>
#include <retro_inline.h>
#include <retro_math.h>
#include <math.h>
#ifndef _XBOX
#define WIN32_LEAN_AND_MEAN
@@ -944,17 +945,66 @@ static void gfx_display_d3d9_hlsl_draw(gfx_display_ctx_draw_t *draw,
y2 = cy + hh;
}
quad[0].x = x1; quad[0].y = y1; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
/* Apply draw->rotation around the quad center. Rotation is
* computed in pixel space (using draw->width / draw->height as
* the icon's true square extents) and then converted back to
* normalised [0,1], so a non-square viewport does not skew the
* rotated icon. */
if (draw->rotation != 0.0f)
{
float cx = (x1 + x2) * 0.5f;
float cy = (y1 + y2) * 0.5f;
float half_w = draw->width * 0.5f;
float half_h = draw->height * 0.5f;
if (draw->scale_factor && draw->scale_factor != 1.0f)
{
half_w *= draw->scale_factor;
half_h *= draw->scale_factor;
}
{
float c = cosf(draw->rotation);
float s = sinf(draw->rotation);
float inv_vw = 1.0f / (float)video_width;
float inv_vh = 1.0f / (float)video_height;
/* Pixel-space corner offsets (dx, dy) for each of the
* four quad vertices. Order matches the (x1,y1)..
* (x2,y2) layout used immediately below. */
float ox[4] = { -half_w, half_w, -half_w, half_w };
float oy[4] = { -half_h, -half_h, half_h, half_h };
float rx[4], ry[4];
int k;
for (k = 0; k < 4; k++)
{
rx[k] = (ox[k] * c - oy[k] * s) * inv_vw;
ry[k] = (ox[k] * s + oy[k] * c) * inv_vh;
}
quad[0].x = cx + rx[0]; quad[0].y = cy + ry[0]; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
quad[1].x = x2; quad[1].y = y1; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[1].x = cx + rx[1]; quad[1].y = cy + ry[1]; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[2].x = x1; quad[2].y = y2; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[2].x = cx + rx[2]; quad[2].y = cy + ry[2]; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[3].x = x2; quad[3].y = y2; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
quad[3].x = cx + rx[3]; quad[3].y = cy + ry[3]; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
}
}
else
{
quad[0].x = x1; quad[0].y = y1; quad[0].z = 0.5f;
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
quad[1].x = x2; quad[1].y = y1; quad[1].z = 0.5f;
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
quad[2].x = x1; quad[2].y = y2; quad[2].z = 0.5f;
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
quad[3].x = x2; quad[3].y = y2; quad[3].z = 0.5f;
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
}
/* Top-down ortho: maps X [0,1]→[-1,1], Y [0,1]→[1,-1] (Y=0 at top).
* Row-major layout for mul(vector, matrix) in the stock HLSL vertex shader.
+61 -19
View File
@@ -20,6 +20,7 @@ SRC(
float4 color1 : COLOR1;
float4 color2 : COLOR2;
float4 color3 : COLOR3;
float2 params : PARAMS;
};
struct PSInput
{
@@ -42,34 +43,75 @@ SRC(
output.color1 = input.color1;
output.color2 = input.color2;
output.color3 = input.color3;
/* params.x is consumed in the VS (scaling, above);
* forward params.y (rotation, in radians) to the GS. */
output.params = input.params;
return output;
}
[maxvertexcount(4)]
void GSMain(point GSInput input[1], inout TriangleStream<PSInput> triStream)
{
/* Apply rotation around the sprite centre.
*
* input[0].position.xy = top-left corner of the quad (clip space)
* input[0].position.zw = quad size (clip space)
*
* The four corners are at `position.xy + position.zw * c`
* with `c` in {(0,0), (1,0), (0,1), (1,1)}. We rewrite as
* `centre + position.zw * (c - 0.5)` and rotate the
* `(c - 0.5)`-scaled offset by `params.y` radians around
* the centre. The rotation is computed in pixel space and
* then mapped back to clip space via the per-axis `.zw`
* sizes; this keeps a square pixel-space icon square on
* screen even on a non-square viewport, because position.z
* and position.w both carry the same pixel-space length W
* (z = 2W/vw, w = 2W/vh). Non-square icons would skew, but
* the only caller of rotation today (the task-message
* hourglass) draws a square. */
PSInput output;
float cosT = cos(input[0].params.y);
float sinT = sin(input[0].params.y);
float2 centre = input[0].position.xy + input[0].position.zw * 0.5;
/* Corner sign ordering matches the four triStream.Append
* calls below; (sx, sy) ∈ {±1} are the corner-relative-to-
* centre signs in pixel space. */
float2 corner_signs[4] = {
float2( 1.0, -1.0), /* top-right -> color0 */
float2(-1.0, -1.0), /* top-left -> color1 */
float2( 1.0, 1.0), /* bottom-right -> color2 */
float2(-1.0, 1.0), /* bottom-left -> color3 */
};
float2 corner_uv[4] = {
float2(1.0, 0.0),
float2(0.0, 0.0),
float2(1.0, 1.0),
float2(0.0, 1.0),
};
float4 corner_colour[4] = {
input[0].color0, input[0].color1,
input[0].color2, input[0].color3,
};
output.position.zw = float2(0.0f, 1.0f);
output.position.xy = input[0].position.xy + input[0].position.zw * float2(1.0, 0.0);
output.texcoord = input[0].texcoord.xy + input[0].texcoord.zw * float2(1.0, 0.0);
output.color = input[0].color0;
triStream.Append(output);
output.position.xy = input[0].position.xy + input[0].position.zw * float2(0.0, 0.0);
output.texcoord = input[0].texcoord.xy + input[0].texcoord.zw * float2(0.0, 0.0);
output.color = input[0].color1;
triStream.Append(output);
output.position.xy = input[0].position.xy + input[0].position.zw * float2(1.0, 1.0);
output.texcoord = input[0].texcoord.xy + input[0].texcoord.zw * float2(1.0, 1.0);
output.color = input[0].color2;
triStream.Append(output);
output.position.xy = input[0].position.xy + input[0].position.zw * float2(0.0, 1.0);
output.texcoord = input[0].texcoord.xy + input[0].texcoord.zw * float2(0.0, 1.0);
output.color = input[0].color3;
triStream.Append(output);
[unroll]
for (int i = 0; i < 4; i++)
{
float sx = corner_signs[i].x;
float sy = corner_signs[i].y;
/* Rotate the half-extent corner offset, then scale back
* by the per-axis clip-space half-size. See block
* comment above for why this preserves square icons. */
float dx = (sx * cosT - sy * sinT) * input[0].position.z * 0.5;
float dy = (sx * sinT + sy * cosT) * input[0].position.w * 0.5;
output.position.xy = centre + float2(dx, dy);
output.texcoord = input[0].texcoord.xy
+ input[0].texcoord.zw * corner_uv[i];
output.color = corner_colour[i];
triStream.Append(output);
}
}
uniform sampler s0;
+7
View File
@@ -1278,6 +1278,13 @@ static void gfx_widgets_draw_task_msg(
texture = MENU_WIDGETS_ICON_HOURGLASS;
color = msg_queue_bar;
radians = msg->hourglass_rotation;
/* The display drivers that consume this rotation via the
* cosine/sine pair (gl, gl1, glcore, vulkan) need the
* trig values recomputed to match the live rotation -
* leaving them at the zero-rotation defaults above pins
* the hourglass icon to its starting orientation. */
cosine = cosf(radians);
sine = sinf(radians);
}
else if (msg->flags & DISPWIDG_FLAG_POSITIVE)
{