/* * Geforce NV2A PGRAPH OpenGL Renderer * * Copyright (c) 2012 espes * Copyright (c) 2015 Jannik Vogel * Copyright (c) 2018-2024 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 . */ #include "qemu/fast-hash.h" #include "hw/xbox/nv2a/nv2a_int.h" #include "debug.h" #include "renderer.h" void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) { PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; NV2A_DPRINTF("---------PRE CLEAR ------\n"); pg->clearing = true; GLbitfield gl_mask = 0; bool write_color = (parameter & NV097_CLEAR_SURFACE_COLOR); bool write_zeta = (parameter & (NV097_CLEAR_SURFACE_Z | NV097_CLEAR_SURFACE_STENCIL)); if (write_zeta) { GLint gl_clear_stencil; GLfloat gl_clear_depth; pgraph_get_clear_depth_stencil_value(pg, &gl_clear_depth, &gl_clear_stencil); if (parameter & NV097_CLEAR_SURFACE_Z) { gl_mask |= GL_DEPTH_BUFFER_BIT; glDepthMask(GL_TRUE); glClearDepth(gl_clear_depth); } if (parameter & NV097_CLEAR_SURFACE_STENCIL) { gl_mask |= GL_STENCIL_BUFFER_BIT; glStencilMask(0xff); glClearStencil(gl_clear_stencil); } } if (write_color) { gl_mask |= GL_COLOR_BUFFER_BIT; glColorMask((parameter & NV097_CLEAR_SURFACE_R) ? GL_TRUE : GL_FALSE, (parameter & NV097_CLEAR_SURFACE_G) ? GL_TRUE : GL_FALSE, (parameter & NV097_CLEAR_SURFACE_B) ? GL_TRUE : GL_FALSE, (parameter & NV097_CLEAR_SURFACE_A) ? GL_TRUE : GL_FALSE); GLfloat rgba[4]; pgraph_get_clear_color(pg, rgba); glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]); } pgraph_gl_surface_update(d, true, write_color, write_zeta); /* FIXME: Needs confirmation */ unsigned int xmin = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CLEARRECTX), NV_PGRAPH_CLEARRECTX_XMIN); unsigned int xmax = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CLEARRECTX), NV_PGRAPH_CLEARRECTX_XMAX); unsigned int ymin = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CLEARRECTY), NV_PGRAPH_CLEARRECTY_YMIN); unsigned int ymax = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CLEARRECTY), NV_PGRAPH_CLEARRECTY_YMAX); NV2A_DPRINTF( "------------------CLEAR 0x%x %d,%d - %d,%d %x---------------\n", parameter, xmin, ymin, xmax, ymax, d->pgraph.regs_[NV_PGRAPH_COLORCLEARVALUE]); unsigned int scissor_width = xmax - xmin + 1, scissor_height = ymax - ymin + 1; pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); ymin = pg->surface_binding_dim.height - (ymin + scissor_height); NV2A_DPRINTF("Translated clear rect to %d,%d - %d,%d\n", xmin, ymin, xmin + scissor_width - 1, ymin + scissor_height - 1); bool full_clear = !xmin && !ymin && scissor_width >= pg->surface_binding_dim.width && scissor_height >= pg->surface_binding_dim.height; pgraph_apply_scaling_factor(pg, &xmin, &ymin); pgraph_apply_scaling_factor(pg, &scissor_width, &scissor_height); /* FIXME: Respect window clip?!?! */ glEnable(GL_SCISSOR_TEST); glScissor(xmin, ymin, scissor_width, scissor_height); /* Dither */ /* FIXME: Maybe also disable it here? + GL implementation dependent */ if (pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & NV_PGRAPH_CONTROL_0_DITHERENABLE) { glEnable(GL_DITHER); } else { glDisable(GL_DITHER); } glClear(gl_mask); glDisable(GL_SCISSOR_TEST); pgraph_gl_set_surface_dirty(pg, write_color, write_zeta); if (r->color_binding) { r->color_binding->cleared = full_clear && write_color; } if (r->zeta_binding) { r->zeta_binding->cleared = full_clear && write_zeta; } pg->clearing = false; } void pgraph_gl_draw_begin(NV2AState *d) { PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; NV2A_GL_DGROUP_BEGIN("NV097_SET_BEGIN_END: 0x%x", pg->primitive_mode); uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); bool mask_alpha = control_0 & NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE; bool mask_red = control_0 & NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE; bool mask_green = control_0 & NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE; bool mask_blue = control_0 & NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE; bool color_write = mask_alpha || mask_red || mask_green || mask_blue; bool depth_test = control_0 & NV_PGRAPH_CONTROL_0_ZENABLE; bool stencil_test = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1) & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE; bool is_nop_draw = !(color_write || depth_test || stencil_test); pgraph_gl_surface_update(d, true, true, depth_test || stencil_test); if (is_nop_draw) { return; } assert(r->color_binding || r->zeta_binding); pgraph_gl_bind_textures(d); pgraph_gl_bind_shaders(pg); glColorMask(mask_red, mask_green, mask_blue, mask_alpha); glDepthMask(!!(control_0 & NV_PGRAPH_CONTROL_0_ZWRITEENABLE)); glStencilMask(GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1), NV_PGRAPH_CONTROL_1_STENCIL_MASK_WRITE)); if (pgraph_reg_r(pg, NV_PGRAPH_BLEND) & NV_PGRAPH_BLEND_EN) { glEnable(GL_BLEND); uint32_t sfactor = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_BLEND), NV_PGRAPH_BLEND_SFACTOR); uint32_t dfactor = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_BLEND), NV_PGRAPH_BLEND_DFACTOR); assert(sfactor < ARRAY_SIZE(pgraph_blend_factor_gl_map)); assert(dfactor < ARRAY_SIZE(pgraph_blend_factor_gl_map)); glBlendFunc(pgraph_blend_factor_gl_map[sfactor], pgraph_blend_factor_gl_map[dfactor]); uint32_t equation = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_BLEND), NV_PGRAPH_BLEND_EQN); assert(equation < ARRAY_SIZE(pgraph_blend_equation_gl_map)); glBlendEquation(pgraph_blend_equation_gl_map[equation]); uint32_t blend_color = pgraph_reg_r(pg, NV_PGRAPH_BLENDCOLOR); float gl_blend_color[4]; pgraph_argb_pack32_to_rgba_float(blend_color, gl_blend_color); glBlendColor(gl_blend_color[0], gl_blend_color[1], gl_blend_color[2], gl_blend_color[3]); } else { glDisable(GL_BLEND); } /* Face culling */ if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_CULLENABLE) { uint32_t cull_face = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER), NV_PGRAPH_SETUPRASTER_CULLCTRL); assert(cull_face < ARRAY_SIZE(pgraph_cull_face_gl_map)); glCullFace(pgraph_cull_face_gl_map[cull_face]); glEnable(GL_CULL_FACE); } else { glDisable(GL_CULL_FACE); } /* Clipping */ glEnable(GL_CLIP_DISTANCE0); glEnable(GL_CLIP_DISTANCE1); /* Front-face select */ glFrontFace(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_FRONTFACE ? GL_CCW : GL_CW); /* Polygon offset */ /* FIXME: GL implementation-specific, maybe do this in VS? */ if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE) { glEnable(GL_POLYGON_OFFSET_FILL); } else { glDisable(GL_POLYGON_OFFSET_FILL); } if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE) { glEnable(GL_POLYGON_OFFSET_LINE); } else { glDisable(GL_POLYGON_OFFSET_LINE); } if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE) { glEnable(GL_POLYGON_OFFSET_POINT); } else { glDisable(GL_POLYGON_OFFSET_POINT); } if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & (NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE | NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE | NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE)) { uint32_t zfactor_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETFACTOR); GLfloat zfactor = *(float*)&zfactor_u32; uint32_t zbias_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETBIAS); GLfloat zbias = *(float*)&zbias_u32; glPolygonOffset(zfactor, zbias); } /* Depth testing */ if (depth_test) { glEnable(GL_DEPTH_TEST); uint32_t depth_func = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0), NV_PGRAPH_CONTROL_0_ZFUNC); assert(depth_func < ARRAY_SIZE(pgraph_depth_func_gl_map)); glDepthFunc(pgraph_depth_func_gl_map[depth_func]); } else { glDisable(GL_DEPTH_TEST); } if (GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_ZCOMPRESSOCCLUDE), NV_PGRAPH_ZCOMPRESSOCCLUDE_ZCLAMP_EN) == NV_PGRAPH_ZCOMPRESSOCCLUDE_ZCLAMP_EN_CLAMP) { glEnable(GL_DEPTH_CLAMP); } else { glDisable(GL_DEPTH_CLAMP); } if (GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), NV_PGRAPH_CONTROL_3_SHADEMODE) == NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT) { glProvokingVertex(GL_FIRST_VERTEX_CONVENTION); } if (stencil_test) { glEnable(GL_STENCIL_TEST); uint32_t stencil_func = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1), NV_PGRAPH_CONTROL_1_STENCIL_FUNC); uint32_t stencil_ref = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1), NV_PGRAPH_CONTROL_1_STENCIL_REF); uint32_t func_mask = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1), NV_PGRAPH_CONTROL_1_STENCIL_MASK_READ); uint32_t op_fail = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_2), NV_PGRAPH_CONTROL_2_STENCIL_OP_FAIL); uint32_t op_zfail = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_2), NV_PGRAPH_CONTROL_2_STENCIL_OP_ZFAIL); uint32_t op_zpass = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_2), NV_PGRAPH_CONTROL_2_STENCIL_OP_ZPASS); assert(stencil_func < ARRAY_SIZE(pgraph_stencil_func_gl_map)); assert(op_fail < ARRAY_SIZE(pgraph_stencil_op_gl_map)); assert(op_zfail < ARRAY_SIZE(pgraph_stencil_op_gl_map)); assert(op_zpass < ARRAY_SIZE(pgraph_stencil_op_gl_map)); glStencilFunc( pgraph_stencil_func_gl_map[stencil_func], stencil_ref, func_mask); glStencilOp( pgraph_stencil_op_gl_map[op_fail], pgraph_stencil_op_gl_map[op_zfail], pgraph_stencil_op_gl_map[op_zpass]); } else { glDisable(GL_STENCIL_TEST); } /* Dither */ /* FIXME: GL implementation dependent */ if (pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & NV_PGRAPH_CONTROL_0_DITHERENABLE) { glEnable(GL_DITHER); } else { glDisable(GL_DITHER); } glEnable(GL_PROGRAM_POINT_SIZE); bool anti_aliasing = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_ANTIALIASING), NV_PGRAPH_ANTIALIASING_ENABLE); /* Edge Antialiasing */ 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)); } else { glDisable(GL_LINE_SMOOTH); glLineWidth(MIN(r->supported_aliased_line_width_range[1], pg->surface_scale_factor)); } if (!anti_aliasing && pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { glEnable(GL_POLYGON_SMOOTH); } else { glDisable(GL_POLYGON_SMOOTH); } unsigned int vp_width = pg->surface_binding_dim.width, vp_height = pg->surface_binding_dim.height; pgraph_apply_scaling_factor(pg, &vp_width, &vp_height); glViewport(0, 0, vp_width, vp_height); /* Surface clip */ /* FIXME: Consider moving to PSH w/ window clip */ unsigned int xmin = pg->surface_shape.clip_x - pg->surface_binding_dim.clip_x, ymin = pg->surface_shape.clip_y - pg->surface_binding_dim.clip_y; unsigned int xmax = xmin + pg->surface_shape.clip_width - 1, ymax = ymin + pg->surface_shape.clip_height - 1; unsigned int scissor_width = xmax - xmin + 1, scissor_height = ymax - ymin + 1; pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); ymin = pg->surface_binding_dim.height - (ymin + scissor_height); pgraph_apply_scaling_factor(pg, &xmin, &ymin); pgraph_apply_scaling_factor(pg, &scissor_width, &scissor_height); glEnable(GL_SCISSOR_TEST); glScissor(xmin, ymin, scissor_width, scissor_height); /* Visibility testing */ if (pg->zpass_pixel_count_enable) { r->gl_zpass_pixel_count_query_count++; r->gl_zpass_pixel_count_queries = (GLuint*)g_realloc( r->gl_zpass_pixel_count_queries, sizeof(GLuint) * r->gl_zpass_pixel_count_query_count); GLuint gl_query; glGenQueries(1, &gl_query); r->gl_zpass_pixel_count_queries[ r->gl_zpass_pixel_count_query_count - 1] = gl_query; glBeginQuery(GL_SAMPLES_PASSED, gl_query); } } void pgraph_gl_draw_end(NV2AState *d) { PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); bool mask_alpha = control_0 & NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE; bool mask_red = control_0 & NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE; bool mask_green = control_0 & NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE; bool mask_blue = control_0 & NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE; bool color_write = mask_alpha || mask_red || mask_green || mask_blue; bool depth_test = control_0 & NV_PGRAPH_CONTROL_0_ZENABLE; bool stencil_test = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1) & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE; bool is_nop_draw = !(color_write || depth_test || stencil_test); if (is_nop_draw) { // FIXME: Check PGRAPH register 0x880. // HW uses bit 11 in 0x880 to enable or disable a color/zeta limit // check that will raise an exception in the case that a draw should // modify the color and/or zeta buffer but the target(s) are masked // off. This check only seems to trigger during the fragment // processing, it is legal to attempt a draw that is entirely // clipped regardless of 0x880. See xemu#635 for context. return; } pgraph_gl_flush_draw(d); /* End of visibility testing */ if (pg->zpass_pixel_count_enable) { nv2a_profile_inc_counter(NV2A_PROF_QUERY); glEndQuery(GL_SAMPLES_PASSED); } pg->draw_time++; if (r->color_binding && pgraph_color_write_enabled(pg)) { r->color_binding->draw_time = pg->draw_time; } if (r->zeta_binding && pgraph_zeta_write_enabled(pg)) { r->zeta_binding->draw_time = pg->draw_time; } pgraph_gl_set_surface_dirty(pg, color_write, depth_test || stencil_test); NV2A_GL_DGROUP_END(); } void pgraph_gl_flush_draw(NV2AState *d) { PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; if (!(r->color_binding || r->zeta_binding)) { return; } assert(r->shader_binding); if (pg->draw_arrays_length) { NV2A_GL_DPRINTF(false, "Draw Arrays"); nv2a_profile_inc_counter(NV2A_PROF_DRAW_ARRAYS); assert(pg->inline_elements_length == 0); assert(pg->inline_buffer_length == 0); assert(pg->inline_array_length == 0); pgraph_gl_bind_vertex_attributes(d, pg->draw_arrays_min_start, pg->draw_arrays_max_count - 1, false, 0, pg->draw_arrays_max_count - 1); glMultiDrawArrays(r->shader_binding->gl_primitive_mode, pg->draw_arrays_start, pg->draw_arrays_count, pg->draw_arrays_length); } else if (pg->inline_elements_length) { NV2A_GL_DPRINTF(false, "Inline Elements"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_ELEMENTS); assert(pg->inline_buffer_length == 0); assert(pg->inline_array_length == 0); uint32_t min_element = (uint32_t)-1; uint32_t max_element = 0; for (int i=0; i < pg->inline_elements_length; i++) { max_element = MAX(pg->inline_elements[i], max_element); min_element = MIN(pg->inline_elements[i], min_element); } pgraph_gl_bind_vertex_attributes( d, min_element, max_element, false, 0, pg->inline_elements[pg->inline_elements_length - 1]); VertexKey k; memset(&k, 0, sizeof(VertexKey)); k.count = pg->inline_elements_length; k.gl_type = GL_UNSIGNED_INT; k.gl_normalize = GL_FALSE; k.stride = sizeof(uint32_t); uint64_t h = fast_hash((uint8_t*)pg->inline_elements, pg->inline_elements_length * 4); LruNode *node = lru_lookup(&r->element_cache, h, &k); VertexLruNode *found = container_of(node, VertexLruNode, node); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, found->gl_buffer); if (!found->initialized) { nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4); glBufferData(GL_ELEMENT_ARRAY_BUFFER, pg->inline_elements_length * 4, pg->inline_elements, GL_STATIC_DRAW); found->initialized = true; } else { nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4_NOTDIRTY); } glDrawElements(r->shader_binding->gl_primitive_mode, pg->inline_elements_length, GL_UNSIGNED_INT, (void *)0); } else if (pg->inline_buffer_length) { NV2A_GL_DPRINTF(false, "Inline Buffer"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_BUFFERS); assert(pg->inline_array_length == 0); if (pg->compressed_attrs) { pg->compressed_attrs = 0; pgraph_gl_bind_shaders(pg); } for (int i = 0; i < NV2A_VERTEXSHADER_ATTRIBUTES; i++) { VertexAttribute *attr = &pg->vertex_attributes[i]; if (attr->inline_buffer_populated) { nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_3); glBindBuffer(GL_ARRAY_BUFFER, r->gl_inline_buffer[i]); glBufferData(GL_ARRAY_BUFFER, pg->inline_buffer_length * sizeof(float) * 4, attr->inline_buffer, GL_STREAM_DRAW); glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(i); attr->inline_buffer_populated = false; memcpy(attr->inline_value, attr->inline_buffer + (pg->inline_buffer_length - 1) * 4, sizeof(attr->inline_value)); } else { glDisableVertexAttribArray(i); glVertexAttrib4fv(i, attr->inline_value); } } glDrawArrays(r->shader_binding->gl_primitive_mode, 0, pg->inline_buffer_length); } else if (pg->inline_array_length) { NV2A_GL_DPRINTF(false, "Inline Array"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_ARRAYS); unsigned int index_count = pgraph_gl_bind_inline_array(d); glDrawArrays(r->shader_binding->gl_primitive_mode, 0, index_count); } else { NV2A_GL_DPRINTF(true, "EMPTY NV097_SET_BEGIN_END"); NV2A_UNCONFIRMED("EMPTY NV097_SET_BEGIN_END"); } }