From d3118a9ae8c76859b50ba2faeca8ed85a4f980f8 Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Sun, 21 May 2017 16:02:46 -0400 Subject: [PATCH] perform a perspective-correct interpolation of each vertex's w component in the ta shader explicitly write out a logarithmic value to the depth buffer inside of the fragment shader based on the interpolated w componet. using the log2(1 + w) / log2(max) provides much better precision in the mantissa compared to w / wmax when normalizing the w values to be written to the depth buffer --- src/hw/pvr/tr.c | 88 +++++++++++++++---------------------- src/hw/pvr/tr.h | 8 ++-- src/render/gl_backend.c | 21 +++++---- src/render/render_backend.h | 5 ++- src/render/ta.glsl | 43 ++++++++++++++---- src/render/ui.glsl | 4 +- src/tracer.c | 49 ++++----------------- 7 files changed, 101 insertions(+), 117 deletions(-) diff --git a/src/hw/pvr/tr.c b/src/hw/pvr/tr.c index f0e5c44b..814bfce4 100644 --- a/src/hw/pvr/tr.c +++ b/src/hw/pvr/tr.c @@ -395,15 +395,11 @@ static void tr_discard_incomplete_surf(struct tr *tr, struct tr_context *rc) { /* * polygon parsing helpers */ -#define PARSE_XYZ(x, y, z, xyz) \ - { \ - xyz[0] = (x); \ - xyz[1] = (y); \ - xyz[2] = (z); \ - if (isfinite(z)) { \ - rc->minz = MIN(rc->minz, xyz[2]); \ - rc->maxz = MAX(rc->maxz, xyz[2]); \ - } \ +#define PARSE_XYZ(x, y, z, xyz) \ + { \ + xyz[0] = (x); \ + xyz[1] = (y); \ + xyz[2] = (z); \ } #define PARSE_COLOR(base_color, color) \ @@ -872,37 +868,6 @@ static void tr_parse_eol(struct tr *tr, const struct tile_context *ctx, tr->vertex_type = TA_NUM_VERTS; } -static void tr_proj_mat(struct tr *tr, const struct tile_context *ctx, - struct tr_context *rc) { - /* this isn't a traditional projection matrix. xy components coming into the - TA are in window space, while the z component is 1/w with +z headed into - the screen. these coordinates need to be scaled back into ndc space, and - z needs to be flipped so that -z is headed into the screen */ - - /* fudge so z isn't mapped to exactly 0.0 and 1.0 */ - float zdepth = (rc->maxz - rc->minz) * 1.2f; - - rc->projection[0] = 2.0f / (float)ctx->video_width; - rc->projection[4] = 0.0f; - rc->projection[8] = 0.0f; - rc->projection[12] = -1.0f; - - rc->projection[1] = 0.0f; - rc->projection[5] = -2.0f / (float)ctx->video_height; - rc->projection[9] = 0.0f; - rc->projection[13] = 1.0f; - - rc->projection[2] = 0.0f; - rc->projection[6] = 0.0f; - rc->projection[10] = 2.0f / -zdepth; - rc->projection[14] = -2.0f * (rc->maxz / -zdepth) - 1.0f; - - rc->projection[3] = 0.0f; - rc->projection[7] = 0.0f; - rc->projection[11] = 0.0f; - rc->projection[15] = 1.0f; -} - static void tr_reset(struct tr *tr, struct tr_context *rc) { /* reset global state */ tr->last_poly = NULL; @@ -911,8 +876,6 @@ static void tr_reset(struct tr *tr, struct tr_context *rc) { tr->vertex_type = TA_NUM_VERTS; /* reset render context state */ - rc->minz = FLT_MAX; - rc->maxz = -FLT_MAX; rc->num_params = 0; rc->num_surfs = 0; rc->num_verts = 0; @@ -923,31 +886,47 @@ static void tr_reset(struct tr *tr, struct tr_context *rc) { } static void tr_render_list(struct render_backend *r, - const struct tr_context *rc, int list_type) { + const struct tr_context *rc, int list_type, int end_surf, int *stopped) { + if (*stopped) { + return; + } + const struct tr_list *list = &rc->lists[list_type]; const int *sorted_surf = list->surfs; const int *sorted_surf_end = list->surfs + list->num_surfs; while (sorted_surf < sorted_surf_end) { - r_draw_ta_surface(r, &rc->surfs[*sorted_surf]); - sorted_surf++; + int surf = *(sorted_surf++); + + r_draw_ta_surface(r, &rc->surfs[surf]); + + if (surf == end_surf) { + *stopped = 1; + break; + } } } -void tr_render_context(struct render_backend *r, const struct tr_context *rc) { - PROF_ENTER("gpu", "tr_render_context"); +void tr_render_context_until(struct render_backend *r, const struct tr_context *rc, int end_surf) { + PROF_ENTER("gpu", "tr_render_context_until"); - r_begin_ta_surfaces(r, rc->projection, rc->verts, rc->num_verts); + int stopped = 0; - tr_render_list(r, rc, TA_LIST_OPAQUE); - tr_render_list(r, rc, TA_LIST_PUNCH_THROUGH); - tr_render_list(r, rc, TA_LIST_TRANSLUCENT); + r_begin_ta_surfaces(r, rc->width, rc->height, rc->verts, rc->num_verts); + + tr_render_list(r, rc, TA_LIST_OPAQUE, end_surf, &stopped); + tr_render_list(r, rc, TA_LIST_PUNCH_THROUGH, end_surf, &stopped); + tr_render_list(r, rc, TA_LIST_TRANSLUCENT, end_surf, &stopped); r_end_ta_surfaces(r); PROF_LEAVE(); } +void tr_render_context(struct render_backend *r, const struct tr_context *rc) { + tr_render_context_until(r, rc, -1); +} + void tr_convert_context(struct render_backend *r, void *userdata, tr_find_texture_cb find_texture, const struct tile_context *ctx, struct tr_context *rc) { @@ -962,7 +941,12 @@ void tr_convert_context(struct render_backend *r, void *userdata, const uint8_t *end = ctx->params + ctx->size; ta_init_tables(); + tr_reset(&tr, rc); + + rc->width = ctx->video_width; + rc->height = ctx->video_height; + tr_parse_bg(&tr, ctx, rc); while (data < end) { @@ -1014,7 +998,5 @@ void tr_convert_context(struct render_backend *r, void *userdata, tr_sort_render_list(&tr, rc, TA_LIST_PUNCH_THROUGH); } - tr_proj_mat(&tr, ctx, rc); - PROF_LEAVE(); } diff --git a/src/hw/pvr/tr.h b/src/hw/pvr/tr.h index 78323fb4..dec79848 100644 --- a/src/hw/pvr/tr.h +++ b/src/hw/pvr/tr.h @@ -52,9 +52,9 @@ struct tr_list { }; struct tr_context { - /* transforms incoming windows space coordinates to ndc space */ - float minz, maxz; - float projection[16]; + /* original video dimensions, needed to project surfaces correctly */ + int width; + int height; /* parsed surfaces and vertices, ready to be passed to the render backend */ struct ta_surface surfs[TA_MAX_SURFS]; @@ -81,5 +81,7 @@ void tr_convert_context(struct render_backend *r, void *userdata, tr_find_texture_cb find_texture, const struct tile_context *ctx, struct tr_context *rc); void tr_render_context(struct render_backend *r, const struct tr_context *rc); +void tr_render_context_until(struct render_backend *r, + const struct tr_context *rc, int end_surf); #endif diff --git a/src/render/gl_backend.c b/src/render/gl_backend.c index 5301220d..389d91fd 100644 --- a/src/render/gl_backend.c +++ b/src/render/gl_backend.c @@ -14,14 +14,15 @@ enum texture_map { }; enum uniform_attr { - UNIFORM_MVP, + UNIFORM_PROJ, UNIFORM_DIFFUSE, + UNIFORM_VIDEO_SCALE, UNIFORM_PT_ALPHA_REF, UNIFORM_NUM_UNIFORMS, }; static const char *uniform_names[] = { - "u_mvp", "u_diffuse", "u_pt_alpha_ref", + "u_proj", "u_diffuse", "u_video_scale", "u_pt_alpha_ref", }; enum shader_attr { @@ -101,7 +102,7 @@ struct render_backend { /* global uniforms that are constant for every surface rendered between a call to begin_surfaces and end_surfaces */ uint64_t uniform_token; - const float *uniform_mvp; + float uniform_video_scale[4]; }; #include "render/ta.glsl" @@ -498,7 +499,7 @@ void r_draw_ta_surface(struct render_backend *r, /* bind global uniforms if they've changed */ if (program->uniform_token != r->uniform_token) { - glUniformMatrix4fv(program->loc[UNIFORM_MVP], 1, GL_FALSE, r->uniform_mvp); + glUniform4fv(program->loc[UNIFORM_VIDEO_SCALE], 1, r->uniform_video_scale); program->uniform_token = r->uniform_token; } @@ -512,11 +513,15 @@ void r_draw_ta_surface(struct render_backend *r, glDrawArrays(GL_TRIANGLE_STRIP, surf->first_vert, surf->num_verts); } -void r_begin_ta_surfaces(struct render_backend *r, const float *projection, - const struct ta_vertex *verts, int num_verts) { +void r_begin_ta_surfaces(struct render_backend *r, int video_width, + int video_height, const struct ta_vertex *verts, + int num_verts) { /* uniforms will be lazily bound for each program inside of r_draw_surface */ r->uniform_token++; - r->uniform_mvp = projection; + r->uniform_video_scale[0] = 2.0f / (float)video_width; + r->uniform_video_scale[1] = -1.0f; + r->uniform_video_scale[2] = -2.0f / (float)video_height; + r->uniform_video_scale[3] = 1.0f; glBindBuffer(GL_ARRAY_BUFFER, r->ta_vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(struct ta_vertex) * num_verts, verts, @@ -593,7 +598,7 @@ void r_begin_ui_surfaces(struct render_backend *r, struct shader_program *program = &r->ui_program; glBindVertexArray(r->ui_vao); glUseProgram(program->prog); - glUniformMatrix4fv(program->loc[UNIFORM_MVP], 1, GL_FALSE, ortho); + glUniformMatrix4fv(program->loc[UNIFORM_PROJ], 1, GL_FALSE, ortho); /* bind buffers */ glBindBuffer(GL_ARRAY_BUFFER, r->ui_vbo); diff --git a/src/render/render_backend.h b/src/render/render_backend.h index 6af8fafc..8351d676 100644 --- a/src/render/render_backend.h +++ b/src/render/render_backend.h @@ -166,8 +166,9 @@ void r_viewport(struct render_backend *r, int width, int height); int r_viewport_width(struct render_backend *r); int r_viewport_height(struct render_backend *r); -void r_begin_ta_surfaces(struct render_backend *r, const float *projection, - const struct ta_vertex *verts, int num_verts); +void r_begin_ta_surfaces(struct render_backend *r, int video_width, + int video_height, const struct ta_vertex *verts, + int num_verts); void r_draw_ta_surface(struct render_backend *r, const struct ta_surface *surf); void r_end_ta_surfaces(struct render_backend *r); diff --git a/src/render/ta.glsl b/src/render/ta.glsl index 9d86136f..ea17064a 100644 --- a/src/render/ta.glsl +++ b/src/render/ta.glsl @@ -1,5 +1,5 @@ static const char *ta_vp = -"uniform mat4 u_mvp;\n" +"uniform vec4 u_video_scale;\n" "layout(location = 0) in vec3 attr_xyz;\n" "layout(location = 1) in vec2 attr_texcoord;\n" @@ -15,14 +15,17 @@ static const char *ta_vp = " var_offset_color = attr_offset_color;\n" " var_texcoord = attr_texcoord;\n" -" // convert from window space back into ndc space\n" -" gl_Position = u_mvp * vec4(attr_xyz, 1.0);\n" +" // scale x from [0,640] -> [-1,1] and y from [0,480] to [-1,1]\n" +" gl_Position.xy = attr_xyz.xy * u_video_scale.xz + u_video_scale.yw;\n" -" // specify w so OpenGL applies perspective corrected texture mapping, but\n" -" // cancel the perspective divide on the xyz, they're already in ndc space\n" -" float w = 1.0 / attr_xyz.z;\n" -" gl_Position.xyz *= w;\n" -" gl_Position.w = w;\n" +" // the z coordinate is actually 1/w, convert to w. note, there is no\n" +" // actual z coordinate provided to the ta, just 1/w. due to this, we set\n" +" // z = w in the vertex shader such that the clip test always passes, and\n" +" // then gl_FragDepth is manually set to w in the fragment shader\n" +" gl_Position.zw = 1.0f / attr_xyz.zz;\n" + +" // cancel the perspective divide on the xy, they're already in ndc space\n" +" gl_Position.xy *= gl_Position.w;\n" "}"; static const char *ta_fp = @@ -70,7 +73,29 @@ static const char *ta_fp = " #ifdef OFFSET_COLOR\n" " fragcolor.rgb += var_offset_color.rgb;\n" " #endif\n" + +" // gl_FragCoord.w is 1/clip.w aka the original 1/w passed to the TA,\n" +" // interpolated in screen space. this value is normally between [0,1],\n" +" // however, values very close to the near plane often raise to 10-100000\n" + +" // unfortunately, there doesn't seem to exist a full 32-bit floating-point\n" +" // depth buffer. because of this, the depth value written out here must be\n" +" // normalized to [0,1] to satisfy OpenGL, which will then subsequently\n" +" // quantize it to a 24-bit integer\n" + +" // if this value is normalized by (w - wmin) / (wmax - wmin), too much\n" +" // precision is ultimately lost in small w values by the time the value is\n" +" // written to the depth buffer. seeing that most values are between [0,1],\n" +" // with only a few outliers being > 1, writing out log2(w) / log2(wmax)\n" +" // works out well to preserve the precision in these smaller w values\n" + +" // note, 2^17 was chosen as ~100000 was largest value i'd seen passed as\n" +" // the w component at the time this was written\n" + +" float w = 1.0 / gl_FragCoord.w;\n" +" gl_FragDepth = log2(1.0 + w) / 17.0;\n" + " #ifdef DEBUG_DEPTH_BUFFER\n" -" fragcolor.rgb = vec3(gl_FragCoord.z);\n" +" fragcolor.rgb = vec3(gl_FragDepth);\n" " #endif\n" "}"; diff --git a/src/render/ui.glsl b/src/render/ui.glsl index b8f6a10c..bb53465b 100644 --- a/src/render/ui.glsl +++ b/src/render/ui.glsl @@ -1,5 +1,5 @@ static const char *ui_vp = -"uniform mat4 u_mvp;\n" +"uniform mat4 u_proj;\n" "layout(location = 0) in vec2 attr_xy;\n" "layout(location = 1) in vec2 attr_texcoord;\n" @@ -11,7 +11,7 @@ static const char *ui_vp = "void main() {\n" " var_color = attr_color;\n" " var_texcoord = attr_texcoord;\n" -" gl_Position = u_mvp * vec4(attr_xy, 0.0, 1.0);\n" +" gl_Position = u_proj * vec4(attr_xy, 0.0, 1.0);\n" "}"; static const char *ui_fp = diff --git a/src/tracer.c b/src/tracer.c index 2360fcea..1a62d118 100644 --- a/src/tracer.c +++ b/src/tracer.c @@ -748,29 +748,6 @@ static void tracer_render_side_menu(struct tracer *tracer) { } } -static void tracer_render_list(struct tracer *tracer, - const struct tr_context *rc, int list_type, - int end, int *stopped) { - if (*stopped) { - return; - } - - const struct tr_list *list = &tracer->rc.lists[list_type]; - const int *sorted_surf = list->surfs; - const int *sorted_surf_end = list->surfs + list->num_surfs; - - while (sorted_surf < sorted_surf_end) { - int idx = *(sorted_surf++); - - r_draw_ta_surface(tracer->r, &tracer->rc.surfs[idx]); - - if (idx == end) { - *stopped = 1; - break; - } - } -} - static void tracer_input_mousemove(void *data, int port, int x, int y) { struct tracer *tracer = data; @@ -802,31 +779,23 @@ void tracer_run_frame(struct tracer *tracer) { nk_update_input(tracer->nk); - /* render ui */ + /* build ui */ tracer_render_side_menu(tracer); tracer_render_scrubber_menu(tracer); tracer_render_debug_menu(tracer); /* render context up to the surface of the currently selected param */ - { - struct tr_context *rc = &tracer->rc; - int stopped = 0; - int end = -1; + struct tr_context *rc = &tracer->rc; + int end_surf = -1; - if (tracer->current_param >= 0) { - struct tr_param *rp = &rc->params[tracer->current_param]; - end = rp->last_surf; - } - - r_begin_ta_surfaces(tracer->r, rc->projection, rc->verts, rc->num_verts); - - tracer_render_list(tracer, rc, TA_LIST_OPAQUE, end, &stopped); - tracer_render_list(tracer, rc, TA_LIST_PUNCH_THROUGH, end, &stopped); - tracer_render_list(tracer, rc, TA_LIST_TRANSLUCENT, end, &stopped); - - r_end_ta_surfaces(tracer->r); + if (tracer->current_param >= 0) { + struct tr_param *rp = &rc->params[tracer->current_param]; + end_surf = rp->last_surf; } + tr_render_context_until(tracer->r, rc, end_surf); + + /* render ui */ nk_render(tracer->nk); }