diff --git a/hw/xbox/nv2a/meson.build b/hw/xbox/nv2a/meson.build index b02ab01d39..871ba02457 100644 --- a/hw/xbox/nv2a/meson.build +++ b/hw/xbox/nv2a/meson.build @@ -9,6 +9,7 @@ specific_ss.add(files( 'pmc.c', 'pramdac.c', 'prmcio.c', + 'prmdio.c', 'prmvio.c', 'psh.c', 'ptimer.c', diff --git a/hw/xbox/nv2a/nv2a.h b/hw/xbox/nv2a/nv2a.h index ad633a39ae..9e3b0206f6 100644 --- a/hw/xbox/nv2a/nv2a.h +++ b/hw/xbox/nv2a/nv2a.h @@ -26,5 +26,6 @@ void nv2a_gl_context_init(void); int nv2a_get_framebuffer_surface(void); void nv2a_set_surface_scale_factor(unsigned int scale); unsigned int nv2a_get_surface_scale_factor(void); +const uint8_t *nv2a_get_dac_palette(void); #endif diff --git a/hw/xbox/nv2a/nv2a_int.h b/hw/xbox/nv2a/nv2a_int.h index c8eb214d95..1308bc30e9 100644 --- a/hw/xbox/nv2a/nv2a_int.h +++ b/hw/xbox/nv2a/nv2a_int.h @@ -250,6 +250,7 @@ typedef struct PGRAPHState { GLint pvideo_enable_loc; GLint pvideo_tex_loc; GLint pvideo_pos_loc; + GLint palette_loc[256]; } disp_rndr; /* subchannels state we're not sure the location of... */ @@ -454,6 +455,11 @@ typedef struct NV2AState { uint32_t fp_hvalid_end; } pramdac; + struct { + uint16_t write_mode_address; + uint8_t palette[256*3]; + } puserdac; + } NV2AState; typedef struct NV2ABlockInfo { diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index de5d30cb05..318b65479f 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -706,6 +706,10 @@ #define NV_PRAMDAC_FP_HCRTC 0x00000828 #define NV_PRAMDAC_FP_HVALID_END 0x00000838 +#define NV_USER_DAC_WRITE_MODE_ADDRESS 0x000003C8 +#define NV_USER_DAC_PALETTE_DATA 0x000003C9 + + #define NV_USER_DMA_PUT 0x40 #define NV_USER_DMA_GET 0x44 #define NV_USER_REF 0x48 diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c index ecbcd90a4d..e76976849b 100644 --- a/hw/xbox/nv2a/pgraph.c +++ b/hw/xbox/nv2a/pgraph.c @@ -4438,7 +4438,6 @@ static void pgraph_init_display_renderer(NV2AState *d) " float y = -1.0 + float((gl_VertexID & 2) << 1);\n" " gl_Position = vec4(x, y, 0, 1);\n" "}\n"; - /* FIXME: gamma correction */ /* FIXME: improve interlace handling, pvideo */ const char *fs = @@ -4681,6 +4680,11 @@ void pgraph_gl_sync(NV2AState *d) qemu_event_set(&d->pgraph.gl_sync_complete); } +const uint8_t *nv2a_get_dac_palette(void) +{ + return g_nv2a->puserdac.palette; +} + int nv2a_get_framebuffer_surface(void) { NV2AState *d = g_nv2a; diff --git a/hw/xbox/nv2a/prmdio.c b/hw/xbox/nv2a/prmdio.c new file mode 100644 index 0000000000..ff04adf661 --- /dev/null +++ b/hw/xbox/nv2a/prmdio.c @@ -0,0 +1,56 @@ +/* + * QEMU Geforce NV2A implementation + * + * Copyright (c) 2021 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "nv2a_int.h" + +uint64_t prmdio_read(void *opaque, hwaddr addr, unsigned int size) +{ + NV2AState *d = (NV2AState *)opaque; + + uint64_t r = 0; + switch (addr) { + case NV_USER_DAC_WRITE_MODE_ADDRESS: + r = d->puserdac.write_mode_address / 3; + break; + default: + break; + } + + nv2a_reg_log_read(NV_PRMDIO, addr, r); + return r; +} + +void prmdio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ + NV2AState *d = (NV2AState *)opaque; + + nv2a_reg_log_write(NV_PRMDIO, addr, val); + + switch (addr) { + case NV_USER_DAC_WRITE_MODE_ADDRESS: + d->puserdac.write_mode_address = (val & 0xff) * 3; + break; + case NV_USER_DAC_PALETTE_DATA: + /* FIXME: Confirm wrap-around */ + d->puserdac.palette[d->puserdac.write_mode_address++ % (256*3)] = val; + break; + default: + break; + } +} diff --git a/hw/xbox/nv2a/stubs.c b/hw/xbox/nv2a/stubs.c index ec3d8e8d85..3638a06108 100644 --- a/hw/xbox/nv2a/stubs.c +++ b/hw/xbox/nv2a/stubs.c @@ -42,7 +42,6 @@ DEFINE_STUB(pcounter, NV_PCOUNTER) DEFINE_STUB(pvpe, NV_PVPE) DEFINE_STUB(ptv, NV_PTV) DEFINE_STUB(prmfb, NV_PRMFB) -DEFINE_STUB(prmdio, NV_PRMDIO) DEFINE_STUB(pstraps, NV_PSTRAPS) // DEFINE_STUB(pramin, NV_PRAMIN) diff --git a/ui/xemu-shaders.c b/ui/xemu-shaders.c index 9f43d06e4a..04279e412d 100644 --- a/ui/xemu-shaders.c +++ b/ui/xemu-shaders.c @@ -83,15 +83,31 @@ struct decal_shader *create_decal_shader(enum SHADER_TYPE type) const char *image_frag_src = "#version 150 core\n" "uniform sampler2D tex;\n" - "uniform vec4 in_ColorPrimary;\n" - "uniform vec4 in_ColorSecondary;\n" - "uniform vec4 in_ColorFill;\n" "in vec2 Texcoord;\n" "out vec4 out_Color;\n" "void main() {\n" - " vec4 t = texture(tex, Texcoord);\n" - " out_Color.rgba = t;\n" + " out_Color.rgba = texture(tex, Texcoord);\n" "}\n"; + + const char *image_gamma_frag_src = + "#version 400 core\n" + "uniform sampler2D tex;\n" + "uniform uint palette[256];\n" + "float gamma_ch(int ch, float col)\n" + "{\n" + " return float(bitfieldExtract(palette[uint(col * 255.0)], ch*8, 8)) / 255.0;\n" + "}\n" + "\n" + "vec4 gamma(vec4 col)\n" + "{\n" + " return vec4(gamma_ch(0, col.r), gamma_ch(1, col.g), gamma_ch(2, col.b), col.a);\n" + "}\n" + "in vec2 Texcoord;\n" + "out vec4 out_Color;\n" + "void main() {\n" + " out_Color.rgba = gamma(texture(tex, Texcoord));\n" + "}\n"; + // Simple 2-color decal shader // - in_ColorFill is first pass // - Red channel of the texture is used as primary color, mixed with 1-Red for @@ -118,6 +134,8 @@ struct decal_shader *create_decal_shader(enum SHADER_TYPE type) frag_src = mask_frag_src; } else if (type == SHADER_TYPE_BLIT) { frag_src = image_frag_src; + } else if (type == SHADER_TYPE_BLIT_GAMMA) { + frag_src = image_gamma_frag_src; } else if (type == SHADER_TYPE_LOGO) { frag_src = xemu_logo_frag_src; } else { @@ -147,6 +165,11 @@ struct decal_shader *create_decal_shader(enum SHADER_TYPE type) s->ColorFill_loc = glGetUniformLocation(s->prog, "in_ColorFill"); s->time_loc = glGetUniformLocation(s->prog, "iTime"); s->scale_loc = glGetUniformLocation(s->prog, "scale"); + for (int i = 0; i < 256; i++) { + char name[64]; + snprintf(name, sizeof(name), "palette[%d]", i); + s->palette_loc[i] = glGetUniformLocation(s->prog, name); + } // Create a vertex array object glGenVertexArrays(1, &s->vao); diff --git a/ui/xemu-shaders.h b/ui/xemu-shaders.h index 37cf340a01..34bf1b8ad3 100644 --- a/ui/xemu-shaders.h +++ b/ui/xemu-shaders.h @@ -27,6 +27,7 @@ enum SHADER_TYPE { SHADER_TYPE_BLIT, + SHADER_TYPE_BLIT_GAMMA, SHADER_TYPE_MASK, SHADER_TYPE_LOGO, }; @@ -52,8 +53,9 @@ struct decal_shader GLint ColorPrimary_loc; GLint ColorSecondary_loc; GLint ColorFill_loc; - GLint time_loc; - GLint scale_loc; + GLint time_loc; + GLint scale_loc; + GLint palette_loc[256]; }; struct fbo { diff --git a/ui/xemu.c b/ui/xemu.c index 8eb7e6d6f3..2558106c3d 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -905,7 +905,7 @@ static void sdl2_display_early_init(DisplayOptions *o) SDL_GL_MakeCurrent(m_window, m_context); SDL_GL_SetSwapInterval(0); xemu_hud_init(m_window, m_context); - blit = create_decal_shader(SHADER_TYPE_BLIT); + blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA); } static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) @@ -1194,6 +1194,14 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) glUniform4f(s->ScaleOffset_loc, scale[0], scale[1], 0, 0); glUniform4f(s->TexScaleOffset_loc, 1.0, 1.0, 0, 0); glUniform1i(s->tex_loc, 0); + + const uint8_t *palette = nv2a_get_dac_palette(); + for (int i = 0; i < 256; i++) { + uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) | + palette[i * 3]; + glUniform1ui(s->palette_loc[i], e); + } + glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);