From 58fcee1f123257acd28dca866f1c9de399880d2c Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 22 Jun 2025 16:40:17 -0700 Subject: [PATCH] nv2a: Implement SET_LINE_WIDTH --- hw/xbox/nv2a/nv2a_regs.h | 2 ++ hw/xbox/nv2a/pgraph/gl/draw.c | 32 +++++++++++++++++++++++++++++-- hw/xbox/nv2a/pgraph/gl/renderer.c | 8 ++++++-- hw/xbox/nv2a/pgraph/gl/renderer.h | 8 ++++++-- hw/xbox/nv2a/pgraph/methods.h.inc | 1 + hw/xbox/nv2a/pgraph/pgraph.c | 17 ++++++++-------- hw/xbox/nv2a/pgraph/pgraph.h | 15 +++++++++++++++ hw/xbox/nv2a/pgraph/vk/draw.c | 3 ++- 8 files changed, 70 insertions(+), 16 deletions(-) diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 206e858b23..3fdd112489 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -503,6 +503,7 @@ # define NV_PGRAPH_SETUPRASTER_POINTSMOOTHENABLE (1 << 9) # define NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE (1 << 10) # define NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE (1 << 11) +# define NV_PGRAPH_SETUPRASTER_LINEWIDTH 0x001ff000 # define NV_PGRAPH_SETUPRASTER_CULLCTRL 0x00600000 # define NV_PGRAPH_SETUPRASTER_CULLCTRL_FRONT 1 # define NV_PGRAPH_SETUPRASTER_CULLCTRL_BACK 2 @@ -1009,6 +1010,7 @@ # define NV097_SET_SHADE_MODE 0x0000037C # define NV097_SET_SHADE_MODE_V_FLAT 0x1D00 # define NV097_SET_SHADE_MODE_V_SMOOTH 0x1D01 +# define NV097_SET_LINE_WIDTH 0x00000380 # define NV097_SET_POLYGON_OFFSET_SCALE_FACTOR 0x00000384 # define NV097_SET_POLYGON_OFFSET_BIAS 0x00000388 # define NV097_SET_FRONT_POLYGON_MODE 0x0000038C diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index 79c18040f9..73916197e7 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -23,6 +23,7 @@ #include "hw/xbox/nv2a/nv2a_int.h" #include "debug.h" #include "renderer.h" +#include void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) { @@ -131,6 +132,32 @@ void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) pg->clearing = false; } +static float clamp_line_width_to_device_limits(PGRAPHState *pg, float width, + bool smooth) +{ + PGRAPHGLState *r = pg->gl_renderer_state; + float min_width; + float max_width; + float granularity; + + if (smooth) { + min_width = r->limits.smooth_line_width.range[0]; + max_width = r->limits.smooth_line_width.range[1]; + granularity = r->limits.smooth_line_width.granularity; + } else { + min_width = r->limits.aliased_line_width.range[0]; + max_width = r->limits.aliased_line_width.range[1]; + granularity = r->limits.aliased_line_width.granularity; + } + + if (granularity != 0.0f) { + float steps = roundf((width - min_width) / granularity); + width = min_width + steps * granularity; + } + + return fminf(fmaxf(min_width, width), max_width); +} + void pgraph_gl_draw_begin(NV2AState *d) { PGRAPHState *pg = &d->pgraph; @@ -310,13 +337,14 @@ void pgraph_gl_draw_begin(NV2AState *d) bool anti_aliasing = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_ANTIALIASING), NV_PGRAPH_ANTIALIASING_ENABLE); /* Edge Antialiasing */ + float line_width = pgraph_get_line_width(pg) * pg->surface_scale_factor; if (!anti_aliasing && pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) { glEnable(GL_LINE_SMOOTH); - glLineWidth(MIN(r->supported_smooth_line_width_range[1], pg->surface_scale_factor)); + glLineWidth(clamp_line_width_to_device_limits(pg, line_width, true)); } else { glDisable(GL_LINE_SMOOTH); - glLineWidth(MIN(r->supported_aliased_line_width_range[1], pg->surface_scale_factor)); + glLineWidth(clamp_line_width_to_device_limits(pg, line_width, false)); } if (!anti_aliasing && pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.c b/hw/xbox/nv2a/pgraph/gl/renderer.c index 36b8029439..bb736294d4 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.c +++ b/hw/xbox/nv2a/pgraph/gl/renderer.c @@ -52,8 +52,12 @@ static void pgraph_gl_init(NV2AState *d, Error **errp) /* Internal RGB565 texture format */ assert(glo_check_extension("GL_ARB_ES2_compatibility")); - glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, r->supported_smooth_line_width_range); - glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, r->supported_aliased_line_width_range); + glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, r->limits.smooth_line_width.range); + glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, + &r->limits.smooth_line_width.granularity); + glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, + r->limits.aliased_line_width.range); + r->limits.aliased_line_width.granularity = 1.0; pgraph_gl_init_surfaces(pg); pgraph_gl_init_reports(d); diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 2a759ba22f..a6fbedf4dd 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -234,8 +234,12 @@ typedef struct PGRAPHGLState { GLint palette_loc[256]; } disp_rndr; - GLfloat supported_aliased_line_width_range[2]; - GLfloat supported_smooth_line_width_range[2]; + struct { + struct { + float range[2]; + float granularity; + } smooth_line_width, aliased_line_width; + } limits; } PGRAPHGLState; extern GloContext *g_nv2a_context_render; diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index d475f9b4b1..235236f145 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -68,6 +68,7 @@ DEF_METHOD(NV097, SET_STENCIL_OP_FAIL) DEF_METHOD(NV097, SET_STENCIL_OP_ZFAIL) DEF_METHOD(NV097, SET_STENCIL_OP_ZPASS) DEF_METHOD(NV097, SET_SHADE_MODE) +DEF_METHOD(NV097, SET_LINE_WIDTH) DEF_METHOD(NV097, SET_POLYGON_OFFSET_SCALE_FACTOR) DEF_METHOD(NV097, SET_POLYGON_OFFSET_BIAS) DEF_METHOD(NV097, SET_FRONT_POLYGON_MODE) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index bfc6a60c30..a5a4364167 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -28,15 +28,6 @@ #include "swizzle.h" #include "nv2a_vsh_emulator.h" -#define PG_GET_MASK(reg, mask) GET_MASK(pgraph_reg_r(pg, reg), mask) -#define PG_SET_MASK(reg, mask, value) \ - do { \ - uint32_t rv = pgraph_reg_r(pg, reg); \ - SET_MASK(rv, mask, value); \ - pgraph_reg_w(pg, reg, rv); \ - } while (0) - - NV2AState *g_nv2a; uint64_t pgraph_read(void *opaque, hwaddr addr, unsigned int size) @@ -1532,6 +1523,14 @@ DEF_METHOD(NV097, SET_SHADE_MODE) } } +DEF_METHOD(NV097, SET_LINE_WIDTH) +{ + if (parameter < 0x200) { + PG_SET_MASK(NV_PGRAPH_SETUPRASTER, NV_PGRAPH_SETUPRASTER_LINEWIDTH, + parameter); + } +} + DEF_METHOD(NV097, SET_POLYGON_OFFSET_SCALE_FACTOR) { pgraph_reg_w(pg, NV_PGRAPH_ZOFFSETFACTOR, parameter); diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index c74e370bba..133462efa9 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -297,6 +297,14 @@ static inline void pgraph_reg_w(PGRAPHState *pg, unsigned int r, uint32_t v) pg->regs_[r] = v; } +#define PG_GET_MASK(reg, mask) GET_MASK(pgraph_reg_r(pg, reg), mask) +#define PG_SET_MASK(reg, mask, value) \ + do { \ + uint32_t rv = pgraph_reg_r(pg, reg); \ + SET_MASK(rv, mask, value); \ + pgraph_reg_w(pg, reg, rv); \ + } while (0) + void pgraph_clear_dirty_reg_map(PGRAPHState *pg); static inline bool pgraph_is_reg_dirty(PGRAPHState *pg, unsigned int reg) @@ -369,6 +377,13 @@ static inline void pgraph_apply_scaling_factor(PGRAPHState *pg, *height *= pg->surface_scale_factor; } +static inline float pgraph_get_line_width(PGRAPHState *pg) +{ + uint32_t line_width = + PG_GET_MASK(NV_PGRAPH_SETUPRASTER, NV_PGRAPH_SETUPRASTER_LINEWIDTH); + return line_width / 8.0f; /* 6.3 format */ +} + void pgraph_get_clear_color(PGRAPHState *pg, float rgba[4]); void pgraph_get_clear_depth_stencil_value(PGRAPHState *pg, float *depth, int *stencil); diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c index 6eea839c44..34065f2b13 100644 --- a/hw/xbox/nv2a/pgraph/vk/draw.c +++ b/hw/xbox/nv2a/pgraph/vk/draw.c @@ -1521,7 +1521,8 @@ static void begin_draw(PGRAPHState *pg) if (r->pipeline_binding->has_dynamic_line_width) { float line_width = - clamp_line_width_to_device_limits(pg, pg->surface_scale_factor); + pgraph_get_line_width(pg) * pg->surface_scale_factor; + line_width = clamp_line_width_to_device_limits(pg, line_width); vkCmdSetLineWidth(r->command_buffer, line_width); } }