aboutsummaryrefslogtreecommitdiffstats
path: root/third_party/webrender/swgl/src/rasterize.h
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/webrender/swgl/src/rasterize.h')
-rw-r--r--third_party/webrender/swgl/src/rasterize.h1670
1 files changed, 0 insertions, 1670 deletions
diff --git a/third_party/webrender/swgl/src/rasterize.h b/third_party/webrender/swgl/src/rasterize.h
deleted file mode 100644
index 48f3b9e5898..00000000000
--- a/third_party/webrender/swgl/src/rasterize.h
+++ /dev/null
@@ -1,1670 +0,0 @@
-/* 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 = left.cur_x() - leftClip * recip(clipStep);
- // 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(l0.y, 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 = (DepthRun*)depthtex.sample_ptr(0, int(y));
- // 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.
- Interpolants step =
- (right.interp - left.interp) * (1.0f / (right.x - left.x));
- // 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(l0.y, 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 = (DepthRun*)depthtex.sample_ptr(0, int(y));
- // 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.
- vec2_scalar stepZW =
- (right.zw() - left.zw()) * (1.0f / (right.x() - left.x()));
- // 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) * (1.0f / (right.x() - left.x()));
- // 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 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;
- p_clip[i] = Point3D(p_clip[i].sel(X, Y, Z) * w * scale + offset, w);
- }
- 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);
- }
- }
- }
- }
-}