Skip to content

Latest commit

 

History

History
220 lines (179 loc) · 11.6 KB

README.md

File metadata and controls

220 lines (179 loc) · 11.6 KB

swgl

Software OpenGL implementation for WebRender

Overview

This is a relatively simple single threaded software rasterizer designed for use by WebRender. It will shade one quad at a time using a 4xf32 vector with one vertex per lane. It rasterizes quads usings spans and shades that span 4 pixels at a time.

Building

clang-cl is required to build on Windows. This can be done by installing the llvm binaries from https://releases.llvm.org/ and adding the installation to the path with something like set PATH=%PATH%;C:\Program Files\LLVM\bin. Then set CC=clang-cl and set CXX=clang-cl. That should be sufficient for cc-rs to use clang-cl instead of cl.

Extensions

SWGL contains a number of OpenGL and GLSL extensions designed to both ease integration with WebRender and to help accelerate span rasterization.

GLSL extension intrinsics are generally prefixed with swgl_ to distinguish them from other items in the GLSL namespace.

Inside GLSL, the SWGL preprocessor token is defined so that usage of SWGL extensions may be conditionally compiled.

void swgl_drawSpanRGBA8();
void swgl_drawSpanR8();

int swgl_SpanLength;
int swgl_StepSize;

mixed swgl_interpStep(mixed varying_input);
void swgl_stepInterp();

SWGL's default fragment processing calls a fragment shader's main function on groups of fragments in units of swgl_StepSize. On return, the value of gl_FragColor is read, packed to an appropriate pixel format, and sent to the blend stage for output to the destination framebuffer. This can be inefficient for some types of fragment shaders, such as those that must lookup from a texture and immediately output it, unpacking the texels only to subsequently repack them at cost. Also, various per-fragment conditions in the shader might need to be repeatedly checked, even though they are actually constant over the entire primitive.

To work around this inefficiency, SWGL allows fragments to optionally be processed over entire spans. This can both side-step the packing inefficiency as well as more efficiently deal with conditions that remain constant over an entire span. SWGL also introduces various specialized intrinsics for more efficiently dealing with certain types of primitive spans with optimal fixed-function processing.

Inside a fragment shader, a swgl_drawSpan function may be defined to override the normal fragment processing for that fragment shader. The function must then call some form of swgl_commit intrinsic to actually output to the destination framebuffer via the blend stage, as normal fragment processing does not take place otherwise as would have happened in main. This function is used by the rasterizer to process an entire span of fragments that have passed the depth test (if applicable) and clipping, but have not yet been output to the blend stage.

The amount of fragments within the span to be processed is designated by swgl_SpanLength and is always aligned to units of swgl_StepSize. The size of a group of fragments in terms of which swgl_commit intrinsics process and output fragments is designated by swgl_StepSize. The swgl_commit intrinsics will deduct accordingly from swgl_SpanLength in units of swgl_StepSize to reflect the fragments actually processed, which may be less than the entire span or up to the entire span.

Fragments should be output until swgl_SpanLength becomes zero to process the entire span. If swgl_drawSpan returns while leaving any fragments unprocessed, the remaining fragments will be processed as normal by the fragment shader's main function. This can be used to conditionally handle certain fast-paths in a fragment shader by otherwise defaulting to the main function if swgl_drawSpan can't appropriately process some or all of the fragments.

The values of any varying inputs to the fragment shader will be set to their values for the start of the span, but do not automatically update over the the course of a span within a given call to swgl_drawSpan. The swgl_interpStep intrinsic may be used to get the derivative per swgl_StepSize group of fragments of a varying input so that the caller may update such variables manually if desired or otherwise use that information for processing. The swgl_stepInterp intrinsic forces all such varying inputs to advance by a single step.

The RGBA8 version will be used when the destination framebuffer is RGBA8 format, and the R8 version will be used when the destination framebuffer is R8. Various other intrinsics described below may have restrictions on whether they can be used only with a certain destination framebuffer format and are noted as such if so.

void swgl_clipMask(sampler2D mask, vec2 offset, vec2 bb_origin, vec2 bb_size);

When called from the the vertex shader, this specifies a clip mask texture to be used to mask the currently drawn primitive while blending is enabled. This mask will only apply to the current primitive.

The mask must be an R8 texture that will be interpreted as alpha weighting applied to the source pixel prior to the blend stage. It is sampled 1:1 with nearest filtering without any applied transform. The given offset specifies the positioning of the clip mask relative to the framebuffer's viewport.

The supplied bounding box constrains sampling of the clip mask to only fall within the given rectangle, specified relative to the clip mask offset. Anything falling outside this rectangle will be clipped entirely. If the rectangle is empty, then the clip mask will be ignored.

void swgl_antiAlias(int edgeMask);

When called from the vertex shader, this enables anti-aliasing for the currently drawn primitive while blending is enabled. This setting will only apply to the current primitive. Anti-aliasing will be applied only to the edges corresponding to bits supplied in the mask. For simple use-cases, the edge mask can be set to all 1 bits to enable AA for the entire quad.

The order of the bits in the edge mask must match the winding order in which the vertices are output in the vertex shader if processed as a quad, so that the edge ends on that vertex. The easiest way to understand this ordering is that for a rectangle (x0,y0,x1,y1) then the edge Nth edge bit corresponds to the edge where Nth coordinate in the rectangle is constant.

SWGL tries to use an anti-aliasing method that is reasonably close to WR's signed-distance field approximation. WR would normally try to discern the 2D local-space coordinates of a given destination pixel relative to the 2D local-space bounding rectangle of a primitive. It then uses the screen- space derivative to try to determine the how many local-space units equate to a distance of around one screen-space pixel. A distance approximation of coverage is then used based on the distance in local-space from the the current pixel's center, roughly at half-intensity at pixel center and ranging to zero or full intensity within a radius of half a pixel away from the center. To account for AAing going outside the normal geometry boundaries of the primitive, WR has to extrude the primitive by a local-space estimate to allow some AA to happen within the extruded region.

SWGL can ultimately do this approximation more simply and get around the extrusion limitations by just ensuring spans encompass any pixel that is partially covered when computing span boundaries. Further, since SWGL already knows the slope of an edge and the coordinate of the span relative to the span boundaries, finding the partial coverage of a given span becomes easy to do without requiring any extra interpolants to track against local-space bounds. Essentially, SWGL just performs anti-aliasing on the actual geometry bounds, but when the pixels on a span's edge are determined to be partially covered during span rasterization, it uses the same distance field method as WR on those span boundary pixels to estimate the coverage based on edge slope.

void swgl_commitTextureLinearRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureLinearR8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureLinearR8ToRGBA8(sampler, vec2 uv, vec4 uv_bounds);

void swgl_commitTextureLinearColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureLinearColorR8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureLinearColorR8ToRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);

void swgl_commitTextureLinearRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureLinearRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);

void swgl_commitTextureNearestRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureNearestColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);

void swgl_commitTextureNearestRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureNearestRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);

void swgl_commitTextureRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);

void swgl_commitTextureRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);

void swgl_commitPartialTextureLinearR8(int len, sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitPartialTextureLinearInvertR8(int len, sampler, vec2 uv, vec4 uv_bounds);

Samples and commits an entire span of texture starting at the given uv and within the supplied uv bounds. The color variations also accept a supplied color that modulates the result.

The RGBA8 versions may only be used to commit within swgl_drawSpanRGBA8, and the R8 versions may only be used to commit within swgl_drawSpanR8. The R8ToRGBA8 versions may be used to sample from an R8 source while committing to an RGBA8 framebuffer.

The Linear variations use a linear filter that bilinearly interpolates between the four samples near the pixel. The Nearest variations use a nearest filter that chooses the closest aliased sample to the center of the pixel. If neither Linear nor Nearest is specified in the swgl_commitTexture variation name, then it will automatically select either the Linear or Nearest variation depending on the sampler's specified filter.

The Repeat variations require an optional repeat rect that specifies how to scale and offset the UVs, assuming the UVs are normalized to repeat in the range 0 to 1. For NearestRepeat variations, it is assumed the repeat rect is always within the bounds. The tile repeat limit, if non-zero, specifies the maximum number of repetitions allowed.

The Partial variations allow committing only a sub-span rather the entire remaining span. These are currently only implemented in linear R8 variants for optimizing clip shaders in WebRender. The Invert variant of these is useful for implementing clip-out modes by inverting the source texture value.

// Premultiplied alpha over blend, but with source color set to source alpha modulated with a constant color.
void swgl_blendDropShadow(vec4 color);
// Premultiplied alpha over blend, but treats the source as a subpixel mask modulated with a constant color.
void swgl_blendSubpixelText(vec4 color);

SWGL allows overriding the blend mode per-primitive by calling swgl_blend intrinsics in the vertex shader. The existing blend mode set by the GL is replaced with the one specified by the intrinsic for the current primitive. The blend mode will be reset to the blend mode set by the GL for the next primitive after the current one, even within the same draw call.