Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// The SWGL depth buffer is roughly organized as a span buffer where each row
// of the depth buffer is a list of spans, and each span has a constant depth
// and a run length (represented by DepthRun). The span from start..start+count
// is placed directly at that start index in the row's array of runs, so that
// there is no need to explicitly record the start index at all. This also
// avoids the need to move items around in the run array to manage insertions
// since space is implicitly always available for a run between any two
// pre-existing runs. Linkage from one run to the next is implicitly defined by
// the count, so if a run exists from start..start+count, the next run will
// implicitly pick up right at index start+count where that preceding run left
// off. All of the DepthRun items that are after the head of the run can remain
// uninitialized until the run needs to be split and a new run needs to start
// somewhere in between.
// For uses like perspective-correct rasterization or with a discard mask, a
// run is not an efficient representation, and it is more beneficial to have
// a flattened array of individual depth samples that can be masked off easily.
// To support this case, the first run in a given row's run array may have a
// zero count, signaling that this entire row is flattened. Critically, the
// depth and count fields in DepthRun are ordered (endian-dependently) so that
// the DepthRun struct can be interpreted as a sign-extended int32_t depth. It
// is then possible to just treat the entire row as an array of int32_t depth
// samples that can be processed with SIMD comparisons, since the count field
// behaves as just the sign-extension of the depth field. The count field is
// limited to 8 bits so that we can support depth values up to 24 bits.
// When a depth buffer is cleared, each row is initialized to a maximal runs
// spanning the entire row. In the normal case, the depth buffer will continue
// to manage itself as a list of runs. If perspective or discard is used for
// a given row, the row will be converted to the flattened representation to
// support it, after which it will only ever revert back to runs if the depth
// buffer is cleared.
// The largest 24-bit depth value supported.
constexpr uint32_t MAX_DEPTH_VALUE = 0xFFFFFF;
// The longest 8-bit depth run that is supported, aligned to SIMD chunk size.
constexpr uint32_t MAX_DEPTH_RUN = 255 & ~3;
struct DepthRun {
// Ensure that depth always occupies the LSB and count the MSB so that we
// can sign-extend depth just by setting count to zero, marking it flat.
// When count is non-zero, then this is interpreted as an actual run and
// depth is read in isolation.
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint32_t depth : 24;
uint32_t count : 8;
#else
uint32_t count : 8;
uint32_t depth : 24;
#endif
DepthRun() = default;
DepthRun(uint32_t depth, uint8_t count) : depth(depth), count(count) {}
// If count is zero, this is actually a flat depth sample rather than a run.
bool is_flat() const { return !count; }
// Compare a source depth from rasterization with a stored depth value.
template <int FUNC>
ALWAYS_INLINE bool compare(uint32_t src) const {
switch (FUNC) {
case GL_LEQUAL:
return src <= depth;
case GL_LESS:
return src < depth;
case GL_ALWAYS:
return true;
default:
assert(false);
return false;
}
}
};
// Fills runs at the given position with the given depth up to the span width.
static ALWAYS_INLINE void set_depth_runs(DepthRun* runs, uint32_t depth,
uint32_t width) {
// If the width exceeds the maximum run size, then we need to output clamped
// runs first.
for (; width >= MAX_DEPTH_RUN;
runs += MAX_DEPTH_RUN, width -= MAX_DEPTH_RUN) {
*runs = DepthRun(depth, MAX_DEPTH_RUN);
}
// If there are still any left over samples to fill under the maximum run
// size, then output one last run for them.
if (width > 0) {
*runs = DepthRun(depth, width);
}
}
// A cursor for reading and modifying a row's depth run array. It locates
// and iterates through a desired span within all the runs, testing if
// the depth of this span passes or fails the depth test against existing
// runs. If desired, new runs may be inserted to represent depth occlusion
// from this span in the run array.
struct DepthCursor {
// Current position of run the cursor has advanced to.
DepthRun* cur = nullptr;
// The start of the remaining potential samples in the desired span.
DepthRun* start = nullptr;
// The end of the potential samples in the desired span.
DepthRun* end = nullptr;
DepthCursor() = default;
// Construct a cursor with runs for a given row's run array and the bounds
// of the span we wish to iterate within it.
DepthCursor(DepthRun* runs, int num_runs, int span_offset, int span_count)
: cur(runs), start(&runs[span_offset]), end(start + span_count) {
// This cursor should never iterate over flat runs
assert(!runs->is_flat());
DepthRun* end_runs = &runs[num_runs];
// Clamp end of span to end of row
if (end > end_runs) {
end = end_runs;
}
// If the span starts past the end of the row, just advance immediately
// to it to signal that we're done.
if (start >= end_runs) {
cur = end_runs;
start = end_runs;
return;
}
// Otherwise, find the first depth run that contains the start of the span.
// If the span starts after the given run, then we need to keep searching
// through the row to find an appropriate run. The check above already
// guaranteed that the span starts within the row's runs, and the search
// won't fall off the end.
for (;;) {
assert(cur < end);
DepthRun* next = cur + cur->count;
if (start < next) {
break;
}
cur = next;
}
}
// The cursor is valid if the current position is at the end or if the run
// contains the start position.
bool valid() const {
return cur >= end || (cur <= start && start < cur + cur->count);
}
// Skip past any initial runs that fail the depth test. If we find a run that
// would pass, then return the accumulated length between where we started
// and that position. Otherwise, if we fall off the end, return -1 to signal
// that there are no more passed runs at the end of this failed region and
// so it is safe for the caller to stop processing any more regions in this
// row.
template <int FUNC>
int skip_failed(uint32_t val) {
assert(valid());
DepthRun* prev = start;
while (cur < end) {
if (cur->compare<FUNC>(val)) {
return start - prev;
}
cur += cur->count;
start = cur;
}
return -1;
}
// Helper to convert function parameters into template parameters to hoist
// some checks out of inner loops.
ALWAYS_INLINE int skip_failed(uint32_t val, GLenum func) {
switch (func) {
case GL_LEQUAL:
return skip_failed<GL_LEQUAL>(val);
case GL_LESS:
return skip_failed<GL_LESS>(val);
default:
assert(false);
return -1;
}
}
// Find a region of runs that passes the depth test. It is assumed the caller
// has called skip_failed first to skip past any runs that failed the depth
// test. This stops when it finds a run that fails the depth test or we fall
// off the end of the row. If the write mask is enabled, this will insert runs
// to represent this new region that passed the depth test. The length of the
// region is returned.
template <int FUNC, bool MASK>
int check_passed(uint32_t val) {
assert(valid());
DepthRun* prev = cur;
while (cur < end) {
if (!cur->compare<FUNC>(val)) {
break;
}
DepthRun* next = cur + cur->count;
if (next > end) {
if (MASK) {
// Chop the current run where the end of the span falls, making a new
// run from the end of the span till the next run. The beginning of
// the current run will be folded into the run from the start of the
// passed region before returning below.
*end = DepthRun(cur->depth, next - end);
}
// If the next run starts past the end, then just advance the current
// run to the end to signal that we're now at the end of the row.
next = end;
}
cur = next;
}
// If we haven't advanced past the start of the span region, then we found
// nothing that passed.
if (cur <= start) {
return 0;
}
// If 'end' fell within the middle of a passing run, then 'cur' will end up
// pointing at the new partial run created at 'end' where the passing run
// was split to accommodate starting in the middle. The preceding runs will
// be fixed below to properly join with this new split.
int passed = cur - start;
if (MASK) {
// If the search started from a run before the start of the span, then
// edit that run to meet up with the start.
if (prev < start) {
prev->count = start - prev;
}
// Create a new run for the entirety of the passed samples.
set_depth_runs(start, val, passed);
}
start = cur;
return passed;
}
// Helper to convert function parameters into template parameters to hoist
// some checks out of inner loops.
template <bool MASK>
ALWAYS_INLINE int check_passed(uint32_t val, GLenum func) {
switch (func) {
case GL_LEQUAL:
return check_passed<GL_LEQUAL, MASK>(val);
case GL_LESS:
return check_passed<GL_LESS, MASK>(val);
default:
assert(false);
return 0;
}
}
ALWAYS_INLINE int check_passed(uint32_t val, GLenum func, bool mask) {
return mask ? check_passed<true>(val, func)
: check_passed<false>(val, func);
}
// Fill a region of runs with a given depth value, bypassing any depth test.
ALWAYS_INLINE void fill(uint32_t depth) {
check_passed<GL_ALWAYS, true>(depth);
}
};
// Initialize a depth texture by setting the first run in each row to encompass
// the entire row.
void Texture::init_depth_runs(uint32_t depth) {
if (!buf) return;
DepthRun* runs = (DepthRun*)buf;
for (int y = 0; y < height; y++) {
set_depth_runs(runs, depth, width);
runs += stride() / sizeof(DepthRun);
}
set_cleared(true);
}
// Fill a portion of the run array with flattened depth samples.
static ALWAYS_INLINE void fill_flat_depth(DepthRun* dst, size_t n,
uint32_t depth) {
fill_n((uint32_t*)dst, n, depth);
}
// Fills a scissored region of a depth texture with a given depth.
void Texture::fill_depth_runs(uint32_t depth, const IntRect& scissor) {
if (!buf) return;
assert(cleared());
IntRect bb = bounds().intersection(scissor - offset);
DepthRun* runs = (DepthRun*)sample_ptr(0, bb.y0);
for (int rows = bb.height(); rows > 0; rows--) {
if (bb.width() >= width) {
// If the scissor region encompasses the entire row, reset the row to a
// single run encompassing the entire row.
set_depth_runs(runs, depth, width);
} else if (runs->is_flat()) {
// If the row is flattened, just directly fill the portion of the row.
fill_flat_depth(&runs[bb.x0], bb.width(), depth);
} else {
// Otherwise, if we are still using runs, then set up a cursor to fill
// it with depth runs.
DepthCursor(runs, width, bb.x0, bb.width()).fill(depth);
}
runs += stride() / sizeof(DepthRun);
}
}
using ZMask = I32;
#if USE_SSE2
# define ZMASK_NONE_PASSED 0xFFFF
# define ZMASK_ALL_PASSED 0
static inline uint32_t zmask_code(ZMask mask) {
return _mm_movemask_epi8(mask);
}
#else
# define ZMASK_NONE_PASSED 0xFFFFFFFFU
# define ZMASK_ALL_PASSED 0
static inline uint32_t zmask_code(ZMask mask) {
return bit_cast<uint32_t>(CONVERT(mask, U8));
}
#endif
// Interprets items in the depth buffer as sign-extended 32-bit depth values
// instead of as runs. Returns a mask that signals which samples in the given
// chunk passed or failed the depth test with given Z value.
template <bool DISCARD>
static ALWAYS_INLINE bool check_depth(I32 src, DepthRun* zbuf, ZMask& outmask,
int span = 4) {
// SSE2 does not support unsigned comparison. So ensure Z value is
// sign-extended to int32_t.
I32 dest = unaligned_load<I32>(zbuf);
// Invert the depth test to check which pixels failed and should be discarded.
ZMask mask = ctx->depthfunc == GL_LEQUAL
?
// GL_LEQUAL: Not(LessEqual) = Greater
ZMask(src > dest)
:
// GL_LESS: Not(Less) = GreaterEqual
ZMask(src >= dest);
// Mask off any unused lanes in the span.
mask |= ZMask(span) < ZMask{1, 2, 3, 4};
if (zmask_code(mask) == ZMASK_NONE_PASSED) {
return false;
}
if (!DISCARD && ctx->depthmask) {
unaligned_store(zbuf, (mask & dest) | (~mask & src));
}
outmask = mask;
return true;
}
static ALWAYS_INLINE I32 packDepth() {
return cast(fragment_shader->gl_FragCoord.z * MAX_DEPTH_VALUE);
}
static ALWAYS_INLINE void discard_depth(I32 src, DepthRun* zbuf, I32 mask) {
if (ctx->depthmask) {
I32 dest = unaligned_load<I32>(zbuf);
mask |= fragment_shader->swgl_IsPixelDiscarded;
unaligned_store(zbuf, (mask & dest) | (~mask & src));
}
}
static ALWAYS_INLINE void mask_output(uint32_t* buf, ZMask zmask,
int span = 4) {
WideRGBA8 r = pack_pixels_RGBA8();
PackedRGBA8 dst = load_span<PackedRGBA8>(buf, span);
if (blend_key) r = blend_pixels(buf, dst, r, span);
PackedRGBA8 mask = bit_cast<PackedRGBA8>(zmask);
store_span(buf, (mask & dst) | (~mask & pack(r)), span);
}
template <bool DISCARD>
static ALWAYS_INLINE void discard_output(uint32_t* buf, int span = 4) {
mask_output(buf, fragment_shader->swgl_IsPixelDiscarded, span);
}
template <>
ALWAYS_INLINE void discard_output<false>(uint32_t* buf, int span) {
WideRGBA8 r = pack_pixels_RGBA8();
if (blend_key)
r = blend_pixels(buf, load_span<PackedRGBA8>(buf, span), r, span);
store_span(buf, pack(r), span);
}
static ALWAYS_INLINE void mask_output(uint8_t* buf, ZMask zmask, int span = 4) {
WideR8 r = pack_pixels_R8();
WideR8 dst = unpack(load_span<PackedR8>(buf, span));
if (blend_key) r = blend_pixels(buf, dst, r, span);
WideR8 mask = packR8(zmask);
store_span(buf, pack((mask & dst) | (~mask & r)), span);
}
template <bool DISCARD>
static ALWAYS_INLINE void discard_output(uint8_t* buf, int span = 4) {
mask_output(buf, fragment_shader->swgl_IsPixelDiscarded, span);
}
template <>
ALWAYS_INLINE void discard_output<false>(uint8_t* buf, int span) {
WideR8 r = pack_pixels_R8();
if (blend_key)
r = blend_pixels(buf, unpack(load_span<PackedR8>(buf, span)), r, span);
store_span(buf, pack(r), span);
}
struct ClipRect {
float x0;
float y0;
float x1;
float y1;
explicit ClipRect(const IntRect& i)
: x0(i.x0), y0(i.y0), x1(i.x1), y1(i.y1) {}
explicit ClipRect(const Texture& t) : ClipRect(ctx->apply_scissor(t)) {
// If blending is enabled, set blend_key to reflect the resolved blend
// state for the currently drawn primitive.
if (ctx->blend) {
blend_key = ctx->blend_key;
if (swgl_ClipFlags) {
// If there is a blend override set, replace the blend key with it.
if (swgl_ClipFlags & SWGL_CLIP_FLAG_BLEND_OVERRIDE) {
blend_key = swgl_BlendOverride;
}
// If a clip mask is available, set up blending state to use the clip
// mask.
if (swgl_ClipFlags & SWGL_CLIP_FLAG_MASK) {
assert(swgl_ClipMask->format == TextureFormat::R8);
// Constrain the clip mask bounds to always fall within the clip mask.
swgl_ClipMaskBounds.intersect(IntRect{0, 0, int(swgl_ClipMask->width),
int(swgl_ClipMask->height)});
// The clip mask offset is relative to the viewport.
swgl_ClipMaskOffset += ctx->viewport.origin() - t.offset;
// The clip mask bounds are relative to the clip mask offset.
swgl_ClipMaskBounds.offset(swgl_ClipMaskOffset);
// Finally, constrain the clip rectangle by the clip mask bounds.
intersect(swgl_ClipMaskBounds);
// Modify the blend key so that it will use the clip mask while
// blending.
restore_clip_mask();
}
if (swgl_ClipFlags & SWGL_CLIP_FLAG_AA) {
// Modify the blend key so that it will use AA while blending.
restore_aa();
}
}
} else {
blend_key = BLEND_KEY_NONE;
swgl_ClipFlags = 0;
}
}
FloatRange x_range() const { return {x0, x1}; }
void intersect(const IntRect& c) {
x0 = max(x0, float(c.x0));
y0 = max(y0, float(c.y0));
x1 = min(x1, float(c.x1));
y1 = min(y1, float(c.y1));
}
template <typename P>
void set_clip_mask(int x, int y, P* buf) const {
if (swgl_ClipFlags & SWGL_CLIP_FLAG_MASK) {
swgl_SpanBuf = buf;
swgl_ClipMaskBuf = (uint8_t*)swgl_ClipMask->buf +
(y - swgl_ClipMaskOffset.y) * swgl_ClipMask->stride +
(x - swgl_ClipMaskOffset.x);
}
}
template <typename P>
bool overlaps(int nump, const P* p) const {
// Generate a mask of which side of the clip rect all of a polygon's points
// fall inside of. This is a cheap conservative estimate of whether the
// bounding box of the polygon might overlap the clip rect, rather than an
// exact test that would require multiple slower line intersections.
int sides = 0;
for (int i = 0; i < nump; i++) {
sides |= p[i].x < x1 ? (p[i].x > x0 ? 1 | 2 : 1) : 2;
sides |= p[i].y < y1 ? (p[i].y > y0 ? 4 | 8 : 4) : 8;
}
return sides == 0xF;
}
};
// Given a current X position at the center Y position of a row, return the X
// position of the left and right intercepts of the row top and bottom.
template <typename E>
static ALWAYS_INLINE FloatRange x_intercepts(const E& e) {
float rad = 0.5f * abs(e.x_slope());
return {e.cur_x() - rad, e.cur_x() + rad};
}
// Return the AA sub-span corresponding to a given edge. If AA is requested,
// then this finds the X intercepts with the row clipped into range of the
// edge and finally conservatively rounds them out. If there is no AA, then
// it just returns the current rounded X position clipped within bounds.
template <typename E>
static ALWAYS_INLINE IntRange aa_edge(const E& e, const FloatRange& bounds) {
return e.edgeMask ? bounds.clip(x_intercepts(e)).round_out()
: bounds.clip({e.cur_x(), e.cur_x()}).round();
}
// Calculate the initial AA coverage as an approximation of the distance from
// the center of the pixel in the direction of the edge slope. Given an edge
// (x,y)..(x+dx,y+dy), then the normalized tangent vector along the edge is
// (dx,dy)/sqrt(dx^2+dy^2). We know that for dy=1 then dx=e.x_slope. We rotate
// the tangent vector either -90 or +90 degrees to get the edge normal vector,
// where 'dx=-dy and 'dy=dx. Once normalized by 1/sqrt(dx^2+dy^2), scale into
// the range of 0..256 so that we can cheaply convert to a fixed-point scale
// factor. It is assumed that at exactly the pixel center the opacity is half
// (128) and linearly decreases along the normal vector at 1:1 scale with the
// slope. While not entirely accurate, this gives a reasonably agreeable looking
// approximation of AA. For edges on which there is no AA, just force the
// opacity to maximum (256) with no slope, relying on the span clipping to trim
// pixels outside the span.
template <typename E>
static ALWAYS_INLINE FloatRange aa_dist(const E& e, float dir) {
if (e.edgeMask) {
float dx = (dir * 256.0f) * inversesqrt(1.0f + e.x_slope() * e.x_slope());
return {128.0f + dx * (e.cur_x() - 0.5f), -dx};
} else {
return {256.0f, 0.0f};
}
}
template <typename P, typename E>
static ALWAYS_INLINE IntRange aa_span(P* buf, const E& left, const E& right,
const FloatRange& bounds) {
// If there is no AA, just return the span from the rounded left edge X
// position to the rounded right edge X position. Clip the span to be within
// the valid bounds.
if (!(swgl_ClipFlags & SWGL_CLIP_FLAG_AA)) {
return bounds.clip({left.cur_x(), right.cur_x()}).round();
}
// Calculate the left and right AA spans along with the coverage distances
// and slopes necessary to do blending.
IntRange leftAA = aa_edge(left, bounds);
FloatRange leftDist = aa_dist(left, -1.0f);
IntRange rightAA = aa_edge(right, bounds);
FloatRange rightDist = aa_dist(right, 1.0f);
// Use the pointer into the destination buffer as a status indicator of the
// coverage offset. The pointer is calculated so that subtracting it with
// the current destination pointer will yield a negative value if the span
// is outside the opaque area and otherwise will yield a positive value
// above the opaque size. This pointer is stored as a uint8 pointer so that
// there are no hidden multiplication instructions and will just return a
// 1:1 linear memory address. Thus the size of the opaque region must also
// be scaled by the pixel size in bytes.
swgl_OpaqueStart = (const uint8_t*)(buf + leftAA.end);
swgl_OpaqueSize = max(rightAA.start - leftAA.end - 3, 0) * sizeof(P);
// Offset the coverage distances by the end of the left AA span, which
// corresponds to the opaque start pointer, so that pixels become opaque
// immediately after. The distances are also offset for each lane in the
// chunk.
Float offset = cast(leftAA.end + (I32){0, 1, 2, 3});
swgl_LeftAADist = leftDist.start + offset * leftDist.end;
swgl_RightAADist = rightDist.start + offset * rightDist.end;
swgl_AASlope =
(Float){leftDist.end, rightDist.end, 0.0f, 0.0f} / float(sizeof(P));
// Return the full span width from the start of the left span to the end of
// the right span.
return {leftAA.start, rightAA.end};
}
// Calculate the span the user clip distances occupy from the left and right
// edges at the current row.
template <typename E>
static ALWAYS_INLINE IntRange clip_distance_range(const E& left,
const E& right) {
Float leftClip = get_clip_distances(left.interp);
Float rightClip = get_clip_distances(right.interp);
// Get the change in clip dist per X step.
Float clipStep = (rightClip - leftClip) / (right.cur_x() - left.cur_x());
// Find the zero intercepts starting from the left edge.
Float clipDist =
clamp(left.cur_x() - leftClip * recip(clipStep), 0.0f, 1.0e6f);
// Find the distance to the start of the span for any clip distances that
// are increasing in value. If the clip distance is constant or decreasing
// in value, then check if it starts outside the clip volume.
Float start = if_then_else(clipStep > 0.0f, clipDist,
if_then_else(leftClip < 0.0f, 1.0e6f, 0.0f));
// Find the distance to the end of the span for any clip distances that are
// decreasing in value. If the clip distance is constant or increasing in
// value, then check if it ends inside the clip volume.
Float end = if_then_else(clipStep < 0.0f, clipDist,
if_then_else(rightClip >= 0.0f, 1.0e6f, 0.0f));
// Find the furthest start offset.
start = max(start, start.zwxy);
// Find the closest end offset.
end = min(end, end.zwxy);
// Finally, round the offsets to an integer span that can be used to bound
// the current span.
return FloatRange{max(start.x, start.y), min(end.x, end.y)}.round();
}
// Converts a run array into a flattened array of depth samples. This just
// walks through every run and fills the samples with the depth value from
// the run.
static void flatten_depth_runs(DepthRun* runs, size_t width) {
if (runs->is_flat()) {
return;
}
while (width > 0) {
size_t n = runs->count;
fill_flat_depth(runs, n, runs->depth);
runs += n;
width -= n;
}
}
// Helper function for drawing passed depth runs within the depth buffer.
// Flattened depth (perspective or discard) is not supported.
template <typename P>
static ALWAYS_INLINE void draw_depth_span(uint32_t z, P* buf,
DepthCursor& cursor) {
for (;;) {
// Get the span that passes the depth test. Assume on entry that
// any failed runs have already been skipped.
int span = cursor.check_passed(z, ctx->depthfunc, ctx->depthmask);
// If nothing passed, since we already skipped passed failed runs
// previously, we must have hit the end of the row. Bail out.
if (span <= 0) {
break;
}
if (span >= 4) {
// If we have a draw specialization, try to process as many 4-pixel
// chunks as possible using it.
if (fragment_shader->has_draw_span(buf)) {
int drawn = fragment_shader->draw_span(buf, span & ~3);
buf += drawn;
span -= drawn;
}
// Otherwise, just process each chunk individually.
while (span >= 4) {
fragment_shader->run();
discard_output<false>(buf);
buf += 4;
span -= 4;
}
}
// If we have a partial chunk left over, we still have to process it as if
// it were a full chunk. Mask off only the part of the chunk we want to
// use.
if (span > 0) {
fragment_shader->run();
discard_output<false>(buf, span);
buf += span;
}
// Skip past any runs that fail the depth test.
int skip = cursor.skip_failed(z, ctx->depthfunc);
// If there aren't any, that means we won't encounter any more passing runs
// and so it's safe to bail out.
if (skip <= 0) {
break;
}
// Advance interpolants for the fragment shader past the skipped region.
// If we processed a partial chunk above, we actually advanced the
// interpolants a full chunk in the fragment shader's run function. Thus,
// we need to first subtract off that 4-pixel chunk and only partially
// advance them to that partial chunk before we can add on the rest of the
// skips. This is combined with the skip here for efficiency's sake.
fragment_shader->skip(skip - (span > 0 ? 4 - span : 0));
buf += skip;
}
}
// Draw a simple span in 4-pixel wide chunks, optionally using depth.
template <bool DISCARD, bool W, typename P, typename Z>
static ALWAYS_INLINE void draw_span(P* buf, DepthRun* depth, int span, Z z) {
if (depth) {
// Depth testing is enabled. If perspective is used, Z values will vary
// across the span, we use packDepth to generate packed Z values suitable
// for depth testing based on current values from gl_FragCoord.z.
// Otherwise, for the no-perspective case, we just use the provided Z.
// Process 4-pixel chunks first.
for (; span >= 4; span -= 4, buf += 4, depth += 4) {
I32 zsrc = z();
ZMask zmask;
if (check_depth<DISCARD>(zsrc, depth, zmask)) {
fragment_shader->run<W>();
mask_output(buf, zmask);
if (DISCARD) discard_depth(zsrc, depth, zmask);
} else {
fragment_shader->skip<W>();
}
}
// If there are any remaining pixels, do a partial chunk.
if (span > 0) {
I32 zsrc = z();
ZMask zmask;
if (check_depth<DISCARD>(zsrc, depth, zmask, span)) {
fragment_shader->run<W>();
mask_output(buf, zmask, span);
if (DISCARD) discard_depth(zsrc, depth, zmask);
}
}
} else {
// Process 4-pixel chunks first.
for (; span >= 4; span -= 4, buf += 4) {
fragment_shader->run<W>();
discard_output<DISCARD>(buf);
}
// If there are any remaining pixels, do a partial chunk.
if (span > 0) {
fragment_shader->run<W>();
discard_output<DISCARD>(buf, span);
}
}
}
// Called during rasterization to forcefully clear a row on which delayed clear
// has been enabled. If we know that we are going to completely overwrite a part
// of the row, then we only need to clear the row outside of that part. However,
// if blending or discard is enabled, the values of that underlying part of the
// row may be used regardless to produce the final rasterization result, so we
// have to then clear the entire underlying row to prepare it.
template <typename P>
static inline void prepare_row(Texture& colortex, int y, int startx, int endx,
bool use_discard, DepthRun* depth,
uint32_t z = 0, DepthCursor* cursor = nullptr) {
assert(colortex.delay_clear > 0);
// Delayed clear is enabled for the color buffer. Check if needs clear.
uint32_t& mask = colortex.cleared_rows[y / 32];
if ((mask & (1 << (y & 31))) == 0) {
mask |= 1 << (y & 31);
colortex.delay_clear--;
if (blend_key || use_discard) {
// If depth test, blending, or discard is used, old color values
// might be sampled, so we need to clear the entire row to fill it.
force_clear_row<P>(colortex, y);
} else if (depth) {
if (depth->is_flat() || !cursor) {
// If flat depth is used, we can't cheaply predict if which samples will
// pass.
force_clear_row<P>(colortex, y);
} else {
// Otherwise if depth runs are used, see how many samples initially pass
// the depth test and only fill the row outside those. The fragment
// shader will fill the row within the passed samples.
int passed =
DepthCursor(*cursor).check_passed<false>(z, ctx->depthfunc);
if (startx > 0 || startx + passed < colortex.width) {
force_clear_row<P>(colortex, y, startx, startx + passed);
}
}
} else if (startx > 0 || endx < colortex.width) {
// Otherwise, we only need to clear the row outside of the span.
// The fragment shader will fill the row within the span itself.
force_clear_row<P>(colortex, y, startx, endx);
}
}
}
// Perpendicular dot-product is the dot-product of a vector with the
// perpendicular vector of the other, i.e. dot(a, {-b.y, b.x})
template <typename T>
static ALWAYS_INLINE auto perpDot(T a, T b) {
return a.x * b.y - a.y * b.x;
}
// Check if the winding of the initial edges is flipped, requiring us to swap
// the edges to avoid spans having negative lengths. Assume that l0.y == r0.y
// due to the initial edge scan in draw_quad/perspective_spans.
template <typename T>
static ALWAYS_INLINE bool checkIfEdgesFlipped(T l0, T l1, T r0, T r1) {
// If the starting point of the left edge is to the right of the starting
// point of the right edge, then just assume the edges are flipped. If the
// left and right starting points are the same, then check the sign of the
// cross-product of the edges to see if the edges are flipped. Otherwise,
// if the left starting point is actually just to the left of the right
// starting point, then assume no edge flip.
return l0.x > r0.x || (l0.x == r0.x && perpDot(l1 - l0, r1 - r0) > 0.0f);
}
// Draw spans for each row of a given quad (or triangle) with a constant Z
// value. The quad is assumed convex. It is clipped to fall within the given
// clip rect. In short, this function rasterizes a quad by first finding a
// top most starting point and then from there tracing down the left and right
// sides of this quad until it hits the bottom, outputting a span between the
// current left and right positions at each row along the way. Points are
// assumed to be ordered in either CW or CCW to support this, but currently
// both orders (CW and CCW) are supported and equivalent.
template <typename P>
static inline void draw_quad_spans(int nump, Point2D p[4], uint32_t z,
Interpolants interp_outs[4],
Texture& colortex, Texture& depthtex,
const ClipRect& clipRect) {
// Only triangles and convex quads supported.
assert(nump == 3 || nump == 4);
Point2D l0, r0, l1, r1;
int l0i, r0i, l1i, r1i;
{
// Find the index of the top-most (smallest Y) point from which
// rasterization can start.
int top = nump > 3 && p[3].y < p[2].y
? (p[0].y < p[1].y ? (p[0].y < p[3].y ? 0 : 3)
: (p[1].y < p[3].y ? 1 : 3))
: (p[0].y < p[1].y ? (p[0].y < p[2].y ? 0 : 2)
: (p[1].y < p[2].y ? 1 : 2));
// Helper to find next index in the points array, walking forward.
#define NEXT_POINT(idx) \
({ \
int cur = (idx) + 1; \
cur < nump ? cur : 0; \
})
// Helper to find the previous index in the points array, walking backward.
#define PREV_POINT(idx) \
({ \
int cur = (idx)-1; \
cur >= 0 ? cur : nump - 1; \
})
// Start looking for "left"-side and "right"-side descending edges starting
// from the determined top point.
int next = NEXT_POINT(top);
int prev = PREV_POINT(top);
if (p[top].y == p[next].y) {
// If the next point is on the same row as the top, then advance one more
// time to the next point and use that as the "left" descending edge.
l0i = next;
l1i = NEXT_POINT(next);
// Assume top and prev form a descending "right" edge, as otherwise this
// will be a collapsed polygon and harmlessly bail out down below.
r0i = top;
r1i = prev;
} else if (p[top].y == p[prev].y) {
// If the prev point is on the same row as the top, then advance to the
// prev again and use that as the "right" descending edge.
// Assume top and next form a non-empty descending "left" edge.
l0i = top;
l1i = next;
r0i = prev;
r1i = PREV_POINT(prev);
} else {
// Both next and prev are on distinct rows from top, so both "left" and
// "right" edges are non-empty/descending.
l0i = r0i = top;
l1i = next;
r1i = prev;
}
// Load the points from the indices.
l0 = p[l0i]; // Start of left edge
r0 = p[r0i]; // End of left edge
l1 = p[l1i]; // Start of right edge
r1 = p[r1i]; // End of right edge
// debugf("l0: %d(%f,%f), r0: %d(%f,%f) -> l1: %d(%f,%f), r1:
// %d(%f,%f)\n", l0i, l0.x, l0.y, r0i, r0.x, r0.y, l1i, l1.x, l1.y, r1i,
// r1.x, r1.y);
}
struct Edge {
float yScale;
float xSlope;
float x;
Interpolants interpSlope;
Interpolants interp;
bool edgeMask;
Edge(float y, const Point2D& p0, const Point2D& p1, const Interpolants& i0,
const Interpolants& i1, int edgeIndex)
: // Inverse Y scale for slope calculations. Avoid divide on 0-length
// edge. Later checks below ensure that Y <= p1.y, or otherwise we
// don't use this edge. We just need to guard against Y == p1.y ==
// p0.y. In that case, Y - p0.y == 0 and will cancel out the slopes
// below, except if yScale is Inf for some reason (or worse, NaN),
// which 1/(p1.y-p0.y) might produce if we don't bound it.
yScale(1.0f / max(p1.y - p0.y, 1.0f / 256)),
// Calculate dX/dY slope
xSlope((p1.x - p0.x) * yScale),
// Initialize current X based on Y and slope
x(p0.x + (y - p0.y) * xSlope),
// Calculate change in interpolants per change in Y
interpSlope((i1 - i0) * yScale),
// Initialize current interpolants based on Y and slope
interp(i0 + (y - p0.y) * interpSlope),
// Extract the edge mask status for this edge
edgeMask((swgl_AAEdgeMask >> edgeIndex) & 1) {}
void nextRow() {
// step current X and interpolants to next row from slope
x += xSlope;
interp += interpSlope;
}
float cur_x() const { return x; }
float x_slope() const { return xSlope; }
};
// Vertex selection above should result in equal left and right start rows
assert(l0.y == r0.y);
// Find the start y, clip to within the clip rect, and round to row center.
// If AA is enabled, round out conservatively rather than round to nearest.
float aaRound = swgl_ClipFlags & SWGL_CLIP_FLAG_AA ? 0.0f : 0.5f;
float y = floor(max(min(l0.y, clipRect.y1), clipRect.y0) + aaRound) + 0.5f;
// Initialize left and right edges from end points and start Y
Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i], l1i);
Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i], r0i);
// WR does not use backface culling, so check if edges are flipped.
bool flipped = checkIfEdgesFlipped(l0, l1, r0, r1);
if (flipped) swap(left, right);
// Get pointer to color buffer and depth buffer at current Y
P* fbuf = (P*)colortex.sample_ptr(0, int(y));
DepthRun* fdepth = depthtex.buf != nullptr
? (DepthRun*)depthtex.sample_ptr(0, int(y))
: nullptr;
// Loop along advancing Ys, rasterizing spans at each row
float checkY = min(min(l1.y, r1.y), clipRect.y1);
// Ensure we don't rasterize out edge bounds
FloatRange clipSpan =
clipRect.x_range().clip(x_range(l0, l1).merge(x_range(r0, r1)));
for (;;) {
// Check if we maybe passed edge ends or outside clip rect...
if (y > checkY) {
// If we're outside the clip rect, we're done.
if (y > clipRect.y1) break;
// Helper to find the next non-duplicate vertex that doesn't loop back.
#define STEP_EDGE(y, e0i, e0, e1i, e1, STEP_POINT, end) \
do { \
/* Set new start of edge to be end of old edge */ \
e0i = e1i; \
e0 = e1; \
/* Set new end of edge to next point */ \
e1i = STEP_POINT(e1i); \
e1 = p[e1i]; \
/* If the edge crossed the end, we're done. */ \
if (e0i == end) return; \
/* Otherwise, it doesn't advance, so keep searching. */ \
} while (y > e1.y)
// Check if Y advanced past the end of the left edge
if (y > l1.y) {
// Step to next left edge past Y and reset edge interpolants.
STEP_EDGE(y, l0i, l0, l1i, l1, NEXT_POINT, r1i);
(flipped ? right : left) =
Edge(y, l0, l1, interp_outs[l0i], interp_outs[l1i], l1i);
}
// Check if Y advanced past the end of the right edge
if (y > r1.y) {
// Step to next right edge past Y and reset edge interpolants.
STEP_EDGE(y, r0i, r0, r1i, r1, PREV_POINT, l1i);
(flipped ? left : right) =
Edge(y, r0, r1, interp_outs[r0i], interp_outs[r1i], r0i);
}
// Reset the clip bounds for the new edges
clipSpan =
clipRect.x_range().clip(x_range(l0, l1).merge(x_range(r0, r1)));
// Reset check condition for next time around.
checkY = min(ceil(min(l1.y, r1.y) - aaRound), clipRect.y1);
}
// Calculate a potentially AA'd span and check if it is non-empty.
IntRange span = aa_span(fbuf, left, right, clipSpan);
if (span.len() > 0) {
// If user clip planes are enabled, use them to bound the current span.
if (vertex_shader->use_clip_distance()) {
span = span.intersect(clip_distance_range(left, right));
if (span.len() <= 0) goto next_span;
}
ctx->shaded_rows++;
ctx->shaded_pixels += span.len();
// Advance color/depth buffer pointers to the start of the span.
P* buf = fbuf + span.start;
// Check if we will need to use depth-buffer or discard on this span.
DepthRun* depth =
depthtex.buf != nullptr && depthtex.cleared() ? fdepth : nullptr;
DepthCursor cursor;
bool use_discard = fragment_shader->use_discard();
if (use_discard) {
if (depth) {
// If we're using discard, we may have to unpredictably drop out some
// samples. Flatten the depth run array here to allow this.
if (!depth->is_flat()) {
flatten_depth_runs(depth, depthtex.width);
}
// Advance to the depth sample at the start of the span.
depth += span.start;
}
} else if (depth) {
if (!depth->is_flat()) {
// We're not using discard and the depth row is still organized into
// runs. Skip past any runs that would fail the depth test so we
// don't have to do any extra work to process them with the rest of
// the span.
cursor = DepthCursor(depth, depthtex.width, span.start, span.len());
int skipped = cursor.skip_failed(z, ctx->depthfunc);
// If we fell off the row, that means we couldn't find any passing
// runs. We can just skip the entire span.
if (skipped < 0) {
goto next_span;
}
buf += skipped;
span.start += skipped;
} else {
// The row is already flattened, so just advance to the span start.
depth += span.start;
}
}
if (colortex.delay_clear) {
// Delayed clear is enabled for the color buffer. Check if needs clear.
prepare_row<P>(colortex, int(y), span.start, span.end, use_discard,
depth, z, &cursor);
}
// Initialize fragment shader interpolants to current span position.
fragment_shader->gl_FragCoord.x = init_interp(span.start + 0.5f, 1);
fragment_shader->gl_FragCoord.y = y;
{
// Change in interpolants is difference between current right and left
// edges per the change in right and left X. If the left and right X
// positions are extremely close together, then avoid stepping the
// interpolants.
float stepScale = 1.0f / (right.x - left.x);
if (!isfinite(stepScale)) stepScale = 0.0f;
Interpolants step = (right.interp - left.interp) * stepScale;
// Advance current interpolants to X at start of span.
Interpolants o = left.interp + step * (span.start + 0.5f - left.x);
fragment_shader->init_span(&o, &step);
}
clipRect.set_clip_mask(span.start, y, buf);
if (!use_discard) {
// Fast paths for the case where fragment discard is not used.
if (depth) {
// If depth is used, we want to process entire depth runs if depth is
// not flattened.
if (!depth->is_flat()) {
draw_depth_span(z, buf, cursor);
goto next_span;
}
// Otherwise, flattened depth must fall back to the slightly slower
// per-chunk depth test path in draw_span below.
} else {
// Check if the fragment shader has an optimized draw specialization.
if (span.len() >= 4 && fragment_shader->has_draw_span(buf)) {
// Draw specialization expects 4-pixel chunks.
int drawn = fragment_shader->draw_span(buf, span.len() & ~3);
buf += drawn;
span.start += drawn;
}
}
draw_span<false, false>(buf, depth, span.len(), [=] { return z; });
} else {
// If discard is used, then use slower fallbacks. This should be rare.
// Just needs to work, doesn't need to be too fast yet...
draw_span<true, false>(buf, depth, span.len(), [=] { return z; });
}
}
next_span:
// Advance Y and edge interpolants to next row.
y++;
left.nextRow();
right.nextRow();
// Advance buffers to next row.
fbuf += colortex.stride() / sizeof(P);
fdepth += depthtex.stride() / sizeof(DepthRun);
}
}
// Draw perspective-correct spans for a convex quad that has been clipped to
// the near and far Z planes, possibly producing a clipped convex polygon with
// more than 4 sides. This assumes the Z value will vary across the spans and
// requires interpolants to factor in W values. This tends to be slower than
// the simpler 2D draw_quad_spans above, especially since we can't optimize the
// depth test easily when Z values, and should be used only rarely if possible.
template <typename P>
static inline void draw_perspective_spans(int nump, Point3D* p,
Interpolants* interp_outs,
Texture& colortex, Texture& depthtex,
const ClipRect& clipRect) {
Point3D l0, r0, l1, r1;
int l0i, r0i, l1i, r1i;
{
// Find the index of the top-most point (smallest Y) from which
// rasterization can start.
int top = 0;
for (int i = 1; i < nump; i++) {
if (p[i].y < p[top].y) {
top = i;
}
}
// Find left-most top point, the start of the left descending edge.
// Advance forward in the points array, searching at most nump points
// in case the polygon is flat.
l0i = top;
for (int i = top + 1; i < nump && p[i].y == p[top].y; i++) {
l0i = i;
}
if (l0i == nump - 1) {
for (int i = 0; i <= top && p[i].y == p[top].y; i++) {
l0i = i;
}
}
// Find right-most top point, the start of the right descending edge.
// Advance backward in the points array, searching at most nump points.
r0i = top;
for (int i = top - 1; i >= 0 && p[i].y == p[top].y; i--) {
r0i = i;
}
if (r0i == 0) {
for (int i = nump - 1; i >= top && p[i].y == p[top].y; i--) {
r0i = i;
}
}
// End of left edge is next point after left edge start.
l1i = NEXT_POINT(l0i);
// End of right edge is prev point after right edge start.
r1i = PREV_POINT(r0i);
l0 = p[l0i]; // Start of left edge
r0 = p[r0i]; // End of left edge
l1 = p[l1i]; // Start of right edge
r1 = p[r1i]; // End of right edge
}
struct Edge {
float yScale;
// Current coordinates for edge. Where in the 2D case of draw_quad_spans,
// it is enough to just track the X coordinate as we advance along the rows,
// for the perspective case we also need to keep track of Z and W. For
// simplicity, we just use the full 3D point to track all these coordinates.
Point3D pSlope;
Point3D p;
Interpolants interpSlope;
Interpolants interp;
bool edgeMask;
Edge(float y, const Point3D& p0, const Point3D& p1, const Interpolants& i0,
const Interpolants& i1, int edgeIndex)
: // Inverse Y scale for slope calculations. Avoid divide on 0-length
// edge.
yScale(1.0f / max(p1.y - p0.y, 1.0f / 256)),
// Calculate dX/dY slope
pSlope((p1 - p0) * yScale),
// Initialize current coords based on Y and slope
p(p0 + (y - p0.y) * pSlope),
// Crucially, these interpolants must be scaled by the point's 1/w
// value, which allows linear interpolation in a perspective-correct
// manner. This will be canceled out inside the fragment shader later.
// Calculate change in interpolants per change in Y
interpSlope((i1 * p1.w - i0 * p0.w) * yScale),
// Initialize current interpolants based on Y and slope
interp(i0 * p0.w + (y - p0.y) * interpSlope),
// Extract the edge mask status for this edge
edgeMask((swgl_AAEdgeMask >> edgeIndex) & 1) {}
float x() const { return p.x; }
vec2_scalar zw() const { return {p.z, p.w}; }
void nextRow() {
// step current coords and interpolants to next row from slope
p += pSlope;
interp += interpSlope;
}
float cur_x() const { return p.x; }
float x_slope() const { return pSlope.x; }
};
// Vertex selection above should result in equal left and right start rows
assert(l0.y == r0.y);
// Find the start y, clip to within the clip rect, and round to row center.
// If AA is enabled, round out conservatively rather than round to nearest.
float aaRound = swgl_ClipFlags & SWGL_CLIP_FLAG_AA ? 0.0f : 0.5f;
float y = floor(max(min(l0.y, clipRect.y1), clipRect.y0) + aaRound) + 0.5f;
// Initialize left and right edges from end points and start Y
Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i], l1i);
Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i], r0i);
// WR does not use backface culling, so check if edges are flipped.
bool flipped = checkIfEdgesFlipped(l0, l1, r0, r1);
if (flipped) swap(left, right);
// Get pointer to color buffer and depth buffer at current Y
P* fbuf = (P*)colortex.sample_ptr(0, int(y));
DepthRun* fdepth = depthtex.buf != nullptr
? (DepthRun*)depthtex.sample_ptr(0, int(y))
: nullptr;
// Loop along advancing Ys, rasterizing spans at each row
float checkY = min(min(l1.y, r1.y), clipRect.y1);
// Ensure we don't rasterize out edge bounds
FloatRange clipSpan =
clipRect.x_range().clip(x_range(l0, l1).merge(x_range(r0, r1)));
for (;;) {
// Check if we maybe passed edge ends or outside clip rect...
if (y > checkY) {
// If we're outside the clip rect, we're done.
if (y > clipRect.y1) break;
// Check if Y advanced past the end of the left edge
if (y > l1.y) {
// Step to next left edge past Y and reset edge interpolants.
STEP_EDGE(y, l0i, l0, l1i, l1, NEXT_POINT, r1i);
(flipped ? right : left) =
Edge(y, l0, l1, interp_outs[l0i], interp_outs[l1i], l1i);
}
// Check if Y advanced past the end of the right edge
if (y > r1.y) {
// Step to next right edge past Y and reset edge interpolants.
STEP_EDGE(y, r0i, r0, r1i, r1, PREV_POINT, l1i);
(flipped ? left : right) =
Edge(y, r0, r1, interp_outs[r0i], interp_outs[r1i], r0i);
}
// Reset the clip bounds for the new edges
clipSpan =
clipRect.x_range().clip(x_range(l0, l1).merge(x_range(r0, r1)));
// Reset check condition for next time around.
checkY = min(ceil(min(l1.y, r1.y) - aaRound), clipRect.y1);
}
// Calculate a potentially AA'd span and check if it is non-empty.
IntRange span = aa_span(fbuf, left, right, clipSpan);
if (span.len() > 0) {
// If user clip planes are enabled, use them to bound the current span.
if (vertex_shader->use_clip_distance()) {
span = span.intersect(clip_distance_range(left, right));
if (span.len() <= 0) goto next_span;
}
ctx->shaded_rows++;
ctx->shaded_pixels += span.len();
// Advance color/depth buffer pointers to the start of the span.
P* buf = fbuf + span.start;
// Check if the we will need to use depth-buffer or discard on this span.
DepthRun* depth =
depthtex.buf != nullptr && depthtex.cleared() ? fdepth : nullptr;
bool use_discard = fragment_shader->use_discard();
if (depth) {
// Perspective may cause the depth value to vary on a per sample basis.
// Ensure the depth row is flattened to allow testing of individual
// samples
if (!depth->is_flat()) {
flatten_depth_runs(depth, depthtex.width);
}
// Advance to the depth sample at the start of the span.
depth += span.start;
}
if (colortex.delay_clear) {
// Delayed clear is enabled for the color buffer. Check if needs clear.
prepare_row<P>(colortex, int(y), span.start, span.end, use_discard,
depth);
}
// Initialize fragment shader interpolants to current span position.
fragment_shader->gl_FragCoord.x = init_interp(span.start + 0.5f, 1);
fragment_shader->gl_FragCoord.y = y;
{
// Calculate the fragment Z and W change per change in fragment X step.
// If the left and right X positions are extremely close together, then
// avoid stepping.
float stepScale = 1.0f / (right.x() - left.x());
if (!isfinite(stepScale)) stepScale = 0.0f;
vec2_scalar stepZW = (right.zw() - left.zw()) * stepScale;
// Calculate initial Z and W values for span start.
vec2_scalar zw = left.zw() + stepZW * (span.start + 0.5f - left.x());
// Set fragment shader's Z and W values so that it can use them to
// cancel out the 1/w baked into the interpolants.
fragment_shader->gl_FragCoord.z = init_interp(zw.x, stepZW.x);
fragment_shader->gl_FragCoord.w = init_interp(zw.y, stepZW.y);
fragment_shader->swgl_StepZW = stepZW;
// Change in interpolants is difference between current right and left
// edges per the change in right and left X. The left and right
// interpolant values were previously multipled by 1/w, so the step and
// initial span values take this into account.
Interpolants step = (right.interp - left.interp) * stepScale;
// Advance current interpolants to X at start of span.
Interpolants o = left.interp + step * (span.start + 0.5f - left.x());
fragment_shader->init_span<true>(&o, &step);
}
clipRect.set_clip_mask(span.start, y, buf);
if (!use_discard) {
// No discard is used. Common case.
draw_span<false, true>(buf, depth, span.len(), packDepth);
} else {
// Discard is used. Rare.
draw_span<true, true>(buf, depth, span.len(), packDepth);
}
}
next_span:
// Advance Y and edge interpolants to next row.
y++;
left.nextRow();
right.nextRow();
// Advance buffers to next row.
fbuf += colortex.stride() / sizeof(P);
fdepth += depthtex.stride() / sizeof(DepthRun);
}
}
// Clip a primitive against both sides of a view-frustum axis, producing
// intermediate vertexes with interpolated attributes that will no longer
// intersect the selected axis planes. This assumes the primitive is convex
// and should produce at most N+2 vertexes for each invocation (only in the
// worst case where one point falls outside on each of the opposite sides
// with the rest of the points inside). The supplied AA edge mask will be
// modified such that it corresponds to the clipped polygon edges.
template <XYZW AXIS>
static int clip_side(int nump, Point3D* p, Interpolants* interp, Point3D* outP,
Interpolants* outInterp, int& outEdgeMask) {
// Potential mask bits of which side of a plane a coordinate falls on.
enum SIDE { POSITIVE = 1, NEGATIVE = 2 };
int numClip = 0;
int edgeMask = outEdgeMask;
Point3D prev = p[nump - 1];
Interpolants prevInterp = interp[nump - 1];
float prevCoord = prev.select(AXIS);
// Coordinate must satisfy -W <= C <= W. Determine if it is outside, and
// if so, remember which side it is outside of. In the special case that W is
// negative and |C| < |W|, both -W <= C and C <= W will be false, such that
// we must consider the coordinate as falling outside of both plane sides
// simultaneously. We test each condition separately and combine them to form
// a mask of which plane sides we exceeded. If we neglect to consider both
// sides simultaneously, points can erroneously oscillate from one plane side
// to the other and exceed the supported maximum number of clip outputs.
int prevMask = (prevCoord < -prev.w ? NEGATIVE : 0) |
(prevCoord > prev.w ? POSITIVE : 0);
// Loop through points, finding edges that cross the planes by evaluating
// the side at each point.
outEdgeMask = 0;
for (int i = 0; i < nump; i++, edgeMask >>= 1) {
Point3D cur = p[i];
Interpolants curInterp = interp[i];
float curCoord = cur.select(AXIS);
int curMask =
(curCoord < -cur.w ? NEGATIVE : 0) | (curCoord > cur.w ? POSITIVE : 0);
// Check if the previous and current end points are on different sides. If
// the masks of sides intersect, then we consider them to be on the same
// side. So in the case the masks do not intersect, we then consider them
// to fall on different sides.
if (!(curMask & prevMask)) {
// One of the edge's end points is outside the plane with the other
// inside the plane. Find the offset where it crosses the plane and
// adjust the point and interpolants to there.
if (prevMask) {
// Edge that was previously outside crosses inside.
// Evaluate plane equation for previous and current end-point
// based on previous side and calculate relative offset.
if (numClip >= nump + 2) {
// If for some reason we produced more vertexes than we support, just
// bail out.
assert(false);
return 0;
}
// The positive plane is assigned the sign 1, and the negative plane is
// assigned -1. If the point falls outside both planes, that means W is
// negative. To compensate for this, we must interpolate the coordinate
// till W=0, at which point we can choose a single plane side for the
// coordinate to fall on since W will no longer be negative. To compute
// the coordinate where W=0, we compute K = prev.w / (prev.w-cur.w) and
// interpolate C = prev.C + K*(cur.C - prev.C). The sign of C will be
// the side of the plane we need to consider. Substituting K into the
// comparison C < 0, we can then avoid the division in K with a
// cross-multiplication.
float prevSide =
(prevMask & NEGATIVE) && (!(prevMask & POSITIVE) ||
prevCoord * (cur.w - prev.w) <
prev.w * (curCoord - prevCoord))
? -1
: 1;
float prevDist = prevCoord - prevSide * prev.w;
float curDist = curCoord - prevSide * cur.w;
// It may happen that after we interpolate by the weight k that due to
// floating point rounding we've underestimated the value necessary to
// push it over the clipping boundary. Just in case, nudge the mantissa
// by a single increment so that we essentially round it up and move it
// further inside the clipping boundary. We use nextafter to do this in
// a portable fashion.
float k = prevDist / (prevDist - curDist);
Point3D clipped = prev + (cur - prev) * k;
if (prevSide * clipped.select(AXIS) > clipped.w) {
k = nextafterf(k, 1.0f);
clipped = prev + (cur - prev) * k;
}
outP[numClip] = clipped;
outInterp[numClip] = prevInterp + (curInterp - prevInterp) * k;
// Don't output the current edge mask since start point was outside.
numClip++;
}
if (curMask) {
// Edge that was previously inside crosses outside.
// Evaluate plane equation for previous and current end-point
// based on current side and calculate relative offset.
if (numClip >= nump + 2) {
assert(false);
return 0;
}
// In the case the coordinate falls on both plane sides, the computation
// here is much the same as for prevSide, but since we are going from a
// previous W that is positive to current W that is negative, then the
// sign of cur.w - prev.w will flip in the equation. The resulting sign
// is negated to compensate for this.
float curSide =
(curMask & POSITIVE) && (!(curMask & NEGATIVE) ||
prevCoord * (cur.w - prev.w) <
prev.w * (curCoord - prevCoord))
? 1
: -1;
float prevDist = prevCoord - curSide * prev.w;
float curDist = curCoord - curSide * cur.w;
// Calculate interpolation weight k and the nudge it inside clipping
// boundary with nextafter. Note that since we were previously inside
// and now crossing outside, we have to flip the nudge direction for
// the weight towards 0 instead of 1.
float k = prevDist / (prevDist - curDist);
Point3D clipped = prev + (cur - prev) * k;
if (curSide * clipped.select(AXIS) > clipped.w) {
k = nextafterf(k, 0.0f);
clipped = prev + (cur - prev) * k;
}
outP[numClip] = clipped;
outInterp[numClip] = prevInterp + (curInterp - prevInterp) * k;
// Output the current edge mask since the end point is inside.
outEdgeMask |= (edgeMask & 1) << numClip;
numClip++;
}
}
if (!curMask) {
// The current end point is inside the plane, so output point unmodified.
if (numClip >= nump + 2) {
assert(false);
return 0;
}
outP[numClip] = cur;
outInterp[numClip] = curInterp;
// Output the current edge mask since the end point is inside.
outEdgeMask |= (edgeMask & 1) << numClip;
numClip++;
}
prev = cur;
prevInterp = curInterp;
prevCoord = curCoord;
prevMask = curMask;
}
return numClip;
}
// Helper function to dispatch to perspective span drawing with points that
// have already been transformed and clipped.
static inline void draw_perspective_clipped(int nump, Point3D* p_clip,
Interpolants* interp_clip,
Texture& colortex,
Texture& depthtex) {
// If polygon is ouside clip rect, nothing to draw.
ClipRect clipRect(colortex);
if (!clipRect.overlaps(nump, p_clip)) {
return;
}
// Finally draw perspective-correct spans for the polygon.
if (colortex.internal_format == GL_RGBA8) {
draw_perspective_spans<uint32_t>(nump, p_clip, interp_clip, colortex,
depthtex, clipRect);
} else if (colortex.internal_format == GL_R8) {
draw_perspective_spans<uint8_t>(nump, p_clip, interp_clip, colortex,
depthtex, clipRect);
} else {
assert(false);
}
}
// Draws a perspective-correct 3D primitive with varying Z value, as opposed
// to a simple 2D planar primitive with a constant Z value that could be
// trivially Z rejected. This requires clipping the primitive against the near
// and far planes to ensure it stays within the valid Z-buffer range. The Z
// and W of each fragment of the primitives are interpolated across the
// generated spans and then depth-tested as appropriate.
// Additionally, vertex attributes must be interpolated with perspective-
// correction by dividing by W before interpolation, and then later multiplied
// by W again to produce the final correct attribute value for each fragment.
// This process is expensive and should be avoided if possible for primitive
// batches that are known ahead of time to not need perspective-correction.
static void draw_perspective(int nump, Interpolants interp_outs[4],
Texture& colortex, Texture& depthtex) {
// Lines are not supported with perspective.
assert(nump >= 3);
// Convert output of vertex shader to screen space.
vec4 pos = vertex_shader->gl_Position;
vec3_scalar scale =
vec3_scalar(ctx->viewport.width(), ctx->viewport.height(), 1) * 0.5f;
vec3_scalar offset =
make_vec3(make_vec2(ctx->viewport.origin() - colortex.offset), 0.0f) +
scale;
// Verify if point is between near and far planes, rejecting NaN.
if (test_all(pos.z > -pos.w && pos.z < pos.w)) {
// No points cross the near or far planes, so no clipping required.
// Just divide coords by W and convert to viewport. We assume the W
// coordinate is non-zero and the reciprocal is finite since it would
// otherwise fail the test_none condition.
Float w = 1.0f / pos.w;
vec3 screen = pos.sel(X, Y, Z) * w * scale + offset;
Point3D p[4] = {{screen.x.x, screen.y.x, screen.z.x, w.x},
{screen.x.y, screen.y.y, screen.z.y, w.y},
{screen.x.z, screen.y.z, screen.z.z, w.z},
{screen.x.w, screen.y.w, screen.z.w, w.w}};
draw_perspective_clipped(nump, p, interp_outs, colortex, depthtex);
} else {
// Points cross the near or far planes, so we need to clip.
// Start with the original 3 or 4 points...
Point3D p[4] = {{pos.x.x, pos.y.x, pos.z.x, pos.w.x},
{pos.x.y, pos.y.y, pos.z.y, pos.w.y},
{pos.x.z, pos.y.z, pos.z.z, pos.w.z},
{pos.x.w, pos.y.w, pos.z.w, pos.w.w}};
// Clipping can expand the points by 1 for each of 6 view frustum planes.
Point3D p_clip[4 + 6];
Interpolants interp_clip[4 + 6];
// Clip against near and far Z planes.
nump = clip_side<Z>(nump, p, interp_outs, p_clip, interp_clip,
swgl_AAEdgeMask);
// If no points are left inside the view frustum, there's nothing to draw.
if (nump < 3) {
return;
}
// After clipping against only the near and far planes, we might still
// produce points where W = 0, exactly at the camera plane. OpenGL specifies
// that for clip coordinates, points must satisfy:
// -W <= X <= W
// -W <= Y <= W
// -W <= Z <= W
// When Z = W = 0, this is trivially satisfied, but when we transform and
// divide by W below it will produce a divide by 0. Usually we want to only
// clip Z to avoid the extra work of clipping X and Y. We can still project
// points that fall outside the view frustum X and Y so long as Z is valid.
// The span drawing code will then ensure X and Y are clamped to viewport
// boundaries. However, in the Z = W = 0 case, sometimes clipping X and Y,
// will push W further inside the view frustum so that it is no longer 0,
// allowing us to finally proceed to projecting the points to the screen.
for (int i = 0; i < nump; i++) {
// Found an invalid W, so need to clip against X and Y...
if (p_clip[i].w <= 0.0f) {
// Ping-pong p_clip -> p_tmp -> p_clip.
Point3D p_tmp[4 + 6];
Interpolants interp_tmp[4 + 6];
nump = clip_side<X>(nump, p_clip, interp_clip, p_tmp, interp_tmp,
swgl_AAEdgeMask);
if (nump < 3) return;
nump = clip_side<Y>(nump, p_tmp, interp_tmp, p_clip, interp_clip,
swgl_AAEdgeMask);
if (nump < 3) return;
// After clipping against X and Y planes, there's still points left
// to draw, so proceed to trying projection now...
break;
}
}
// Divide coords by W and convert to viewport.
for (int i = 0; i < nump; i++) {
float w = 1.0f / p_clip[i].w;
// If the W coord is essentially zero, small enough that division would
// result in Inf/NaN, then just set the point to all zeroes, as the only
// point that satisfies -W <= X/Y/Z <= W is all zeroes.
p_clip[i] = isfinite(w)
? Point3D(p_clip[i].sel(X, Y, Z) * w * scale + offset, w)
: Point3D(0.0f);
}
draw_perspective_clipped(nump, p_clip, interp_clip, colortex, depthtex);
}
}
static void draw_quad(int nump, Texture& colortex, Texture& depthtex) {
// Run vertex shader once for the primitive's vertices.
// Reserve space for 6 sets of interpolants, in case we need to clip against
// near and far planes in the perspective case.
Interpolants interp_outs[4];
swgl_ClipFlags = 0;
vertex_shader->run_primitive((char*)interp_outs, sizeof(Interpolants));
vec4 pos = vertex_shader->gl_Position;
// Check if any vertex W is different from another. If so, use perspective.
if (test_any(pos.w != pos.w.x)) {
draw_perspective(nump, interp_outs, colortex, depthtex);
return;
}
// Convert output of vertex shader to screen space.
// Divide coords by W and convert to viewport.
float w = 1.0f / pos.w.x;
// If the W coord is essentially zero, small enough that division would
// result in Inf/NaN, then just set the reciprocal itself to zero so that
// the coordinates becomes zeroed out, as the only valid point that
// satisfies -W <= X/Y/Z <= W is all zeroes.
if (!isfinite(w)) w = 0.0f;
vec2 screen = (pos.sel(X, Y) * w + 1) * 0.5f *
vec2_scalar(ctx->viewport.width(), ctx->viewport.height()) +
make_vec2(ctx->viewport.origin() - colortex.offset);
Point2D p[4] = {{screen.x.x, screen.y.x},
{screen.x.y, screen.y.y},
{screen.x.z, screen.y.z},
{screen.x.w, screen.y.w}};
// If quad is ouside clip rect, nothing to draw.
ClipRect clipRect(colortex);
if (!clipRect.overlaps(nump, p)) {
return;
}
// Since the quad is assumed 2D, Z is constant across the quad.
float screenZ = (pos.z.x * w + 1) * 0.5f;
if (screenZ < 0 || screenZ > 1) {
// Z values would cross the near or far plane, so just bail.
return;
}
// Since Z doesn't need to be interpolated, just set the fragment shader's
// Z and W values here, once and for all fragment shader invocations.
uint32_t z = uint32_t(MAX_DEPTH_VALUE * screenZ);
fragment_shader->gl_FragCoord.z = screenZ;
fragment_shader->gl_FragCoord.w = w;
// If supplied a line, adjust it so that it is a quad at least 1 pixel thick.
// Assume that for a line that all 4 SIMD lanes were actually filled with
// vertexes 0, 1, 1, 0.
if (nump == 2) {
// Nudge Y height to span at least 1 pixel by advancing to next pixel
// boundary so that we step at least 1 row when drawing spans.
if (int(p[0].y + 0.5f) == int(p[1].y + 0.5f)) {
p[2].y = 1 + int(p[1].y + 0.5f);
p[3].y = p[2].y;
// Nudge X width to span at least 1 pixel so that rounded coords fall on
// separate pixels.
if (int(p[0].x + 0.5f) == int(p[1].x + 0.5f)) {
p[1].x += 1.0f;
p[2].x += 1.0f;
}
} else {
// If the line already spans at least 1 row, then assume line is vertical
// or diagonal and just needs to be dilated horizontally.
p[2].x += 1.0f;
p[3].x += 1.0f;
}
// Pretend that it's a quad now...
nump = 4;
}
// Finally draw 2D spans for the quad. Currently only supports drawing to
// RGBA8 and R8 color buffers.
if (colortex.internal_format == GL_RGBA8) {
draw_quad_spans<uint32_t>(nump, p, z, interp_outs, colortex, depthtex,
clipRect);
} else if (colortex.internal_format == GL_R8) {
draw_quad_spans<uint8_t>(nump, p, z, interp_outs, colortex, depthtex,
clipRect);
} else {
assert(false);
}
}
template <typename INDEX>
static inline void draw_elements(GLsizei count, GLsizei instancecount,
size_t offset, VertexArray& v,
Texture& colortex, Texture& depthtex) {
Buffer& indices_buf = ctx->buffers[v.element_array_buffer_binding];
if (!indices_buf.buf || offset >= indices_buf.size) {
return;
}
assert((offset & (sizeof(INDEX) - 1)) == 0);
INDEX* indices = (INDEX*)(indices_buf.buf + offset);
count = min(count, (GLsizei)((indices_buf.size - offset) / sizeof(INDEX)));
// Triangles must be indexed at offsets 0, 1, 2.
// Quads must be successive triangles indexed at offsets 0, 1, 2, 2, 1, 3.
if (count == 6 && indices[1] == indices[0] + 1 &&
indices[2] == indices[0] + 2 && indices[5] == indices[0] + 3) {
assert(indices[3] == indices[0] + 2 && indices[4] == indices[0] + 1);
// Fast path - since there is only a single quad, we only load per-vertex
// attribs once for all instances, as they won't change across instances
// or within an instance.
vertex_shader->load_attribs(v.attribs, indices[0], 0, 4);
draw_quad(4, colortex, depthtex);
for (GLsizei instance = 1; instance < instancecount; instance++) {
vertex_shader->load_attribs(v.attribs, indices[0], instance, 0);
draw_quad(4, colortex, depthtex);
}
} else {
for (GLsizei instance = 0; instance < instancecount; instance++) {
for (GLsizei i = 0; i + 3 <= count; i += 3) {
if (indices[i + 1] != indices[i] + 1 ||
indices[i + 2] != indices[i] + 2) {
continue;
}
if (i + 6 <= count && indices[i + 5] == indices[i] + 3) {
assert(indices[i + 3] == indices[i] + 2 &&
indices[i + 4] == indices[i] + 1);
vertex_shader->load_attribs(v.attribs, indices[i], instance, 4);
draw_quad(4, colortex, depthtex);
i += 3;
} else {
vertex_shader->load_attribs(v.attribs, indices[i], instance, 3);
draw_quad(3, colortex, depthtex);
}
}
}
}
}