From b21e399bd09577d2eef78d81c0db642ba4c390b7 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Mon, 11 Oct 2021 21:55:39 -0400 Subject: Fix many texture bugs, access memory in increasing order everywhere - switch z_buffer to use screen space coordinates (why does y increase downward!) - extract pointless (re)calculations from inside hot loops, in particular from object drawing but also to the extent allowed by applicable law from floor and ceiling drawing --- src/render.zig | 127 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 59 deletions(-) (limited to 'src/render.zig') diff --git a/src/render.zig b/src/render.zig index 12af775..ae9983c 100644 --- a/src/render.zig +++ b/src/render.zig @@ -174,28 +174,30 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { const inv_height = 1 / height; - var tex_frac: f32 = std.math.clamp((start - left) / width, 0, 1); + const tex_x_step = constants.TextureDim / width; + const toff = obj.texture * @floatToInt(c_uint, constants.TextureDim); + const thresh = top - height; + const constrained_bottom = std.math.min(top, PlaneHeight); + const pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_bottom - 1, 0))); + var tex_x: f32 = std.math.clamp((start - left) / width, 0, 1) * constants.TextureDim; var col: usize = @floatToInt(usize, start); - const tex_frac_step = 1 / width; + var bottom: f32 = 0; while (col < end) : ({ col += 1; - tex_frac += tex_frac_step; + tex_x += tex_x_step; + bottom = constrained_bottom; }) { - var bottom = std.math.min(top, PlaneHeight); - var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - bottom - 1, 0))); + const tx = @floatToInt(c_uint, tex_x); + var pix_index = @floatToInt(usize, PlaneWidth) * pix_y + col; var texel_y = (top - bottom) / height; - while (pix_y < PlaneHeight and bottom >= top - height) : ({ + while (pix_index < PlanePixels and bottom > thresh) : ({ bottom -= 1; - pix_y += 1; texel_y += inv_height; + pix_index += @floatToInt(usize, PlaneWidth); }) { - const index = col * @floatToInt(usize, PlaneHeight) + @floatToInt(usize, bottom); - if (self.z_buffer[index] > scaled_perp_distance) { - const tx = @floatToInt(c_uint, tex_frac * constants.TextureDim); - const toff = obj.texture * @floatToInt(c_uint, constants.TextureDim); + if (self.z_buffer[pix_index] > scaled_perp_distance) { const ty = @floatToInt(c_uint, texel_y * constants.TextureDim); const texel = objects_image.getPixel(.{ .x = toff + tx, .y = ty }); - const pix_index = @floatToInt(usize, PlaneWidth) * pix_y + col; // TODO: Decide whether being accurate is as important as being fast pixels[pix_index] = fasterColourBlend(pixels[pix_index], texel); } @@ -312,7 +314,7 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { // we also want to be sure that we're consistently orienting // textures, in this case clockwise if ((horizontal_hit and sinra < 0) or (!horizontal_hit and cosra > 0)) texfrac = 1 - texfrac; - const texstrip = @floatToInt(c_uint, (constants.TextureDim - 1) * texfrac); + const texstrip = @floatToInt(c_uint, constants.TextureDim * texfrac); // height of a unit-height wall at this distance const nominal_length = PlaneDist / distance; @@ -324,16 +326,18 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { // which texture index? const t_lower_off = cell.lower_texture * td; // Note the bizarre rounding we have to do to avoid artifacts - const constrained_top = std.math.min(std.math.floor(top_of_floor), std.math.ceil(lowest_drawn)); - const stop = @floatToInt(i32, highest_drawn); - var zb_y = @floatToInt(i32, constrained_top); + const constrained_top = std.math.floor(std.math.min(top_of_floor, lowest_drawn)); + const proj_height = cell.floor_height * nominal_length; + const constrained_bottom = std.math.max(highest_drawn, top_of_floor - proj_height); + const stop = @floatToInt(usize, PlaneHeight - constrained_bottom); var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); - var texel_y = (top_of_floor - constrained_top) / nominal_length; + var texel_y: f32 = std.math.max((top_of_floor - constrained_top) / nominal_length, 0); // now we have what we need to draw the face, - // and update the z-buffer - while (zb_y > stop) : ({ - zb_y -= 1; + // and update the z-buffer. Note that it doesn't + // matter which way we write to the z_buffer + // here, walls are vertical! + while (pix_y < stop) : ({ pix_y += 1; texel_y += inv_nom_len; }) { @@ -342,41 +346,40 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; pixels[pix_index] = texel; - - const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + zb_y); - self.z_buffer[index] = distance; + self.z_buffer[pix_index] = distance; } - highest_drawn = top_of_floor; + highest_drawn = constrained_top; } if (draw_upper) { const proj_default_end = PlaneHeight / 2 + PlaneDist * (level.Cell.DEFAULT_HEIGHT - pheight) / distance; - const stop = @floatToInt(i32, std.math.min(lowest_drawn, proj_default_end)); - const t_upper_off = cell.upper_texture * td; + const constrained_top = std.math.min(lowest_drawn, proj_default_end); const constrained_bottom = std.math.max(bottom_of_ceiling, highest_drawn); - var zb_y = @floatToInt(i32, constrained_bottom); - var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_bottom - 1, 0))); - var texel_y: f32 = 0; - while (zb_y < stop) : ({ - zb_y += 1; - pix_y -= 1; + const stop = @floatToInt(i32, PlaneHeight - constrained_bottom); + const t_upper_off = cell.upper_texture * td; + var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); + // TODO: Decide if textures should be pinned at + // the top or bottom. Currently they are pinned + // at the top. For bottom this would read: + // constants.TextureDim - (constrained_top - constrained_bottom) / nominal_length + var texel_y: f32 = (proj_default_end - constrained_top) / nominal_length; + while (pix_y < stop) : ({ + pix_y += 1; texel_y += inv_nom_len; }) { - const ty = @floatToInt(c_uint, (1 - std.math.modf(texel_y).fpart) * (constants.TextureDim - 1)); + const ty = @floatToInt(c_uint, std.math.modf(texel_y).fpart * constants.TextureDim); const texel = walls_image.getPixel(.{ .x = t_upper_off + texstrip, .y = ty }); const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; pixels[pix_index] = texel; - - const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + zb_y); - self.z_buffer[index] = distance; + self.z_buffer[pix_index] = distance; } - lowest_drawn = bottom_of_ceiling; + lowest_drawn = constrained_bottom; } } } - // do we potentially draw floor for this cell? + // do we potentially draw floor and or ceiling for this cell? if (highest_drawn < PlaneHeight / 2 or (cell.draw_down and lowest_drawn > PlaneHeight / 2)) { if (dist_y < dist_x) { distance = dist_y; @@ -395,52 +398,58 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { // draw floor? if (next_top > highest_drawn) { - top_of_floor = std.math.ceil(highest_drawn); - const thresh = std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1); - while (top_of_floor < thresh) : (top_of_floor += 1) { + const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim); + + top_of_floor = std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1); + const thresh = std.math.ceil(highest_drawn); + + const itop = @floatToInt(usize, std.math.max(top_of_floor, 0)); + const ptop = @floatToInt(usize, PlaneHeight) - itop - 1; + var pix_index = ptop * @floatToInt(usize, PlaneWidth) + col; + while (top_of_floor > thresh) : ({ + top_of_floor -= 1; + pix_index += @floatToInt(usize, PlaneWidth); + }) { const row_dist = (pheight - cell.floor_height) * PlaneDist / (PlaneHeight / 2 - top_of_floor); - const ptop = @floatToInt(usize, top_of_floor); - const itop = @floatToInt(usize, PlaneHeight) - ptop - 1; - // draw the correct pixel const sx = std.math.modf(ppos_x + row_dist * cosra); const sy = std.math.modf(ppos_y + row_dist * sinra); - const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim); const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart)); const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart)); const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py }); - pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val; + pixels[pix_index] = val; // record in the z_buffer only if we're above the floor! - if (cell.floor_height > 0) { - const index = col * @floatToInt(usize, PlaneHeight) + ptop; - self.z_buffer[index] = row_dist; - } + if (cell.floor_height > 0) self.z_buffer[pix_index] = row_dist; } highest_drawn = next_top; } // draw ceiling? if (cell.draw_down and next_bottom < lowest_drawn) { + const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim); + bottom_of_ceiling = std.math.ceil(lowest_drawn); const thresh = std.math.max(std.math.max(next_bottom, highest_drawn), PlaneHeight / 2 - 1); - while (bottom_of_ceiling > thresh) : (bottom_of_ceiling -= 1) { + + const itop = @floatToInt(usize, bottom_of_ceiling); + const ptop = @floatToInt(usize, PlaneHeight) - itop - 1; + var pix_index = ptop * @floatToInt(usize, PlaneWidth) + col; + + while (bottom_of_ceiling > thresh) : ({ + bottom_of_ceiling -= 1; + pix_index += @floatToInt(usize, PlaneWidth); + }) { const row_dist = (cell.ceiling_height - pheight) * PlaneDist / (bottom_of_ceiling - PlaneHeight / 2); - const ptop = @floatToInt(usize, bottom_of_ceiling); - const itop = @floatToInt(usize, PlaneHeight) - ptop; const sx = std.math.modf(ppos_x + row_dist * cosra); const sy = std.math.modf(ppos_y + row_dist * sinra); - const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim); const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart)); const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart)); const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py }); - pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val; + pixels[pix_index] = val; - if (cell.draw_down) { - const index = col * @floatToInt(usize, PlaneHeight) + ptop; - self.z_buffer[index] = row_dist; - } + if (cell.draw_down) self.z_buffer[pix_index] = row_dist; } lowest_drawn = next_bottom; } -- cgit v1.3.1 From 1a00dd2342292e545d3aa26c2c0eadc80caedf5c Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Tue, 12 Oct 2021 11:35:17 -0400 Subject: No need to recalculate the index inside each loop Here's hoping Zig is smart enough (TM) to constant-ify all of these @floatToInt(type, PlaneXXX) values. --- src/main.zig | 2 +- src/render.zig | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) (limited to 'src/render.zig') diff --git a/src/main.zig b/src/main.zig index 1211529..01a021c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -71,7 +71,7 @@ pub fn main() !void { .lower_texture = 1, .upper_texture = 1, .floor_texture = 1, - .ceiling_texture = 1, + .ceiling_texture = 2, .draw_down = true, }; diff --git a/src/render.zig b/src/render.zig index ae9983c..00e65fd 100644 --- a/src/render.zig +++ b/src/render.zig @@ -329,22 +329,22 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { const constrained_top = std.math.floor(std.math.min(top_of_floor, lowest_drawn)); const proj_height = cell.floor_height * nominal_length; const constrained_bottom = std.math.max(highest_drawn, top_of_floor - proj_height); - const stop = @floatToInt(usize, PlaneHeight - constrained_bottom); - var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); + const stop = @floatToInt(usize, PlaneWidth * (PlaneHeight - constrained_bottom)) + col; + const pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); + var pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; var texel_y: f32 = std.math.max((top_of_floor - constrained_top) / nominal_length, 0); // now we have what we need to draw the face, // and update the z-buffer. Note that it doesn't // matter which way we write to the z_buffer // here, walls are vertical! - while (pix_y < stop) : ({ - pix_y += 1; + while (pix_index < stop) : ({ + pix_index += @floatToInt(usize, PlaneWidth); texel_y += inv_nom_len; }) { const ty = @floatToInt(c_uint, std.math.modf(texel_y).fpart * constants.TextureDim); const texel = walls_image.getPixel(.{ .x = t_lower_off + texstrip, .y = ty }); - const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; pixels[pix_index] = texel; self.z_buffer[pix_index] = distance; } @@ -355,22 +355,18 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { const proj_default_end = PlaneHeight / 2 + PlaneDist * (level.Cell.DEFAULT_HEIGHT - pheight) / distance; const constrained_top = std.math.min(lowest_drawn, proj_default_end); const constrained_bottom = std.math.max(bottom_of_ceiling, highest_drawn); - const stop = @floatToInt(i32, PlaneHeight - constrained_bottom); + const stop = @floatToInt(usize, PlaneWidth * (PlaneHeight - constrained_bottom)) + col; const t_upper_off = cell.upper_texture * td; - var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); - // TODO: Decide if textures should be pinned at - // the top or bottom. Currently they are pinned - // at the top. For bottom this would read: - // constants.TextureDim - (constrained_top - constrained_bottom) / nominal_length - var texel_y: f32 = (proj_default_end - constrained_top) / nominal_length; - while (pix_y < stop) : ({ - pix_y += 1; + const pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); + var pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; + var texel_y: f32 = constants.TextureDim - (constrained_top - constrained_bottom) / nominal_length; + while (pix_index < stop) : ({ + pix_index += @floatToInt(usize, PlaneWidth); texel_y += inv_nom_len; }) { const ty = @floatToInt(c_uint, std.math.modf(texel_y).fpart * constants.TextureDim); const texel = walls_image.getPixel(.{ .x = t_upper_off + texstrip, .y = ty }); - const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col; pixels[pix_index] = texel; self.z_buffer[pix_index] = distance; } -- cgit v1.3.1 From 2463aa73cb926bee528e0882bea28b3c2dcf6e60 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Tue, 12 Oct 2021 11:57:23 -0400 Subject: More texture bugs, hoorah Might as well also compute texel_y directly like texel_x for objects and skip a multiplication in favour of addition --- src/render.zig | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'src/render.zig') diff --git a/src/render.zig b/src/render.zig index 00e65fd..47753f0 100644 --- a/src/render.zig +++ b/src/render.zig @@ -172,10 +172,10 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { const start = std.math.max(0, left); const end = @floatToInt(usize, std.math.min(left + width, PlaneWidth - 1)); - const inv_height = 1 / height; - + const tex_y_step = constants.TextureDim / height; const tex_x_step = constants.TextureDim / width; const toff = obj.texture * @floatToInt(c_uint, constants.TextureDim); + const thresh = top - height; const constrained_bottom = std.math.min(top, PlaneHeight); const pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_bottom - 1, 0))); @@ -189,14 +189,14 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { }) { const tx = @floatToInt(c_uint, tex_x); var pix_index = @floatToInt(usize, PlaneWidth) * pix_y + col; - var texel_y = (top - bottom) / height; + var texel_y = constants.TextureDim * (top - bottom) / height; while (pix_index < PlanePixels and bottom > thresh) : ({ bottom -= 1; - texel_y += inv_height; + texel_y += tex_y_step; pix_index += @floatToInt(usize, PlaneWidth); }) { if (self.z_buffer[pix_index] > scaled_perp_distance) { - const ty = @floatToInt(c_uint, texel_y * constants.TextureDim); + const ty = @floatToInt(c_uint, texel_y); const texel = objects_image.getPixel(.{ .x = toff + tx, .y = ty }); // TODO: Decide whether being accurate is as important as being fast pixels[pix_index] = fasterColourBlend(pixels[pix_index], texel); @@ -318,8 +318,8 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { // height of a unit-height wall at this distance const nominal_length = PlaneDist / distance; - const inv_nom_len = distance / PlaneDist; // used for texel indexing + const inv_nom_len = distance / PlaneDist; const td = @floatToInt(c_uint, constants.TextureDim); if (draw_lower) { @@ -335,9 +335,7 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { var texel_y: f32 = std.math.max((top_of_floor - constrained_top) / nominal_length, 0); // now we have what we need to draw the face, - // and update the z-buffer. Note that it doesn't - // matter which way we write to the z_buffer - // here, walls are vertical! + // and update the z-buffer. while (pix_index < stop) : ({ pix_index += @floatToInt(usize, PlaneWidth); texel_y += inv_nom_len; @@ -354,7 +352,7 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { if (draw_upper) { const proj_default_end = PlaneHeight / 2 + PlaneDist * (level.Cell.DEFAULT_HEIGHT - pheight) / distance; const constrained_top = std.math.min(lowest_drawn, proj_default_end); - const constrained_bottom = std.math.max(bottom_of_ceiling, highest_drawn); + const constrained_bottom = std.math.ceil(std.math.max(bottom_of_ceiling, highest_drawn)); const stop = @floatToInt(usize, PlaneWidth * (PlaneHeight - constrained_bottom)) + col; const t_upper_off = cell.upper_texture * td; const pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top - 1, 0))); @@ -396,7 +394,7 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type { if (next_top > highest_drawn) { const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim); - top_of_floor = std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1); + top_of_floor = std.math.ceil(std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1)); const thresh = std.math.ceil(highest_drawn); const itop = @floatToInt(usize, std.math.max(top_of_floor, 0)); -- cgit v1.3.1