blob: 098a46e4c2eb208d7455e77dcc8f065803586d72 [file] [log] [blame]
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// OverlayDraw.comp: Draw overlay widgets. A maximum of 32 text widgets and 32 graph widgets is
// supported simultaneously.
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
#if Is8x4
#define BLOCK_WIDTH 8
#define BLOCK_HEIGHT 4
#elif Is8x8
#define BLOCK_WIDTH 8
#define BLOCK_HEIGHT 8
#else
#error "Not all subgroup sizes are accounted for"
#endif
// Limits:
// Note that max length and size is defined such that each buffer length is below 16KB, the minimum
// guaranteed supported size for uniform buffers.
#define MAX_TEXT_WIDGETS 32
#define MAX_GRAPH_WIDGETS 32
#define MAX_TEXT_LENGTH 256
#define MAX_GRAPH_SIZE 64
// Font information:
#define FONT_GLYPHS_PER_ROW 32
#define FONT_GLYPHS_ROWS 3
#define FONT_CHARACTERS (FONT_GLYPHS_PER_ROW * FONT_GLYPHS_ROWS)
layout (local_size_x = BLOCK_WIDTH, local_size_y = BLOCK_HEIGHT, local_size_z = 1) in;
struct TextWidgetData
{
uvec4 coordinates;
vec4 color;
uvec4 fontSize; // w unused. xy has the font glyph width/height. z has the layer.
uvec4 text[MAX_TEXT_LENGTH / 16];
};
struct GraphWidgetData
{
uvec4 coordinates;
vec4 color;
uvec4 valueWidth; // yzw unused. x should necessarily divide coordinate's z-x
uvec4 values[MAX_GRAPH_SIZE / 4];
};
layout(set = 0, binding = 0, rgba32f) uniform image2D blendOutput;
layout (set = 0, binding = 1) uniform TextWidgets
{
TextWidgetData textWidgetsData[MAX_TEXT_WIDGETS];
};
layout (set = 0, binding = 2) uniform GraphWidgets
{
GraphWidgetData graphWidgetsData[MAX_GRAPH_WIDGETS];
};
layout(set = 0, binding = 3) uniform utexture2D culledWidgets;
layout(set = 0, binding = 4) uniform texture2DArray font;
layout (push_constant) uniform PushConstants
{
uvec2 outputSize;
} params;
bool intersects(const uvec2 imageCoords, const uvec4 widgetCoords)
{
return all(greaterThanEqual(imageCoords, widgetCoords.xy)) &&
all(lessThan(imageCoords, widgetCoords.zw));
}
uint getChar(const uint textWidget, const uvec2 coordInWidget, const uint fontGlyphWidth)
{
const uint charIndex = coordInWidget.x / fontGlyphWidth;
const uint packIndex = charIndex / 4;
const uint packedChars = textWidgetsData[textWidget].text[packIndex / 4][packIndex % 4];
const uint shift = (charIndex % 4) * 8;
#if IsBigEndian
return (packedChars >> (24 - shift)) & 0xFF;
#else
return (packedChars >> shift) & 0xFF;
#endif
}
float sampleFont(const uint textChar,
const uvec2 coordInWidget,
const uvec2 fontGlyphSize,
const uint fontLayer)
{
const uvec2 coordInGlyph = coordInWidget % fontGlyphSize;
const uvec2 glyphOffset = fontGlyphSize *
uvec2(textChar % FONT_GLYPHS_PER_ROW, textChar / FONT_GLYPHS_PER_ROW);
return texelFetch(font, ivec3(glyphOffset + coordInGlyph, fontLayer), 0).x;
}
uint getValue(const uint graphWidget, const uvec2 coordInWidget, const uint valueWidth)
{
const uint valueIndex = coordInWidget.x / valueWidth.x;
return graphWidgetsData[graphWidget].values[valueIndex / 4][valueIndex % 4];
}
vec4 blend(const vec4 blendedSoFar, const vec4 color)
{
// Assuming colors (C1, a1), (C2, a2) ... (Cn, an) have been so far blended, blendedSoFar will
// contain:
//
// .rgb = Cn*an + ... + C2*a2*(1-a3)*...*(1-an) + C1*a1*(1-a2)*...*(1-an)
// .a = (1-a1)*(1-a2)*...*(1-an)
//
// Blending with (Cn+1, an+1) is simply:
//
// .rgb = Cn+1*an+1 + .rgb*(1-an+1)
// .a = .a*(1-an+1)
//
// Note that finally, the background color will be multipled by .a and added with .rgb.
return vec4(blendedSoFar.rgb * (1 - color.a) + color.rgb * color.a,
blendedSoFar.a * (1 - color.a));
}
void main()
{
const uvec2 imageCoords = gl_GlobalInvocationID.xy;
if (any(greaterThanEqual(imageCoords, params.outputSize)))
{
return;
}
vec4 blendedWidgets = vec4(0, 0, 0, 1);
const uvec2 subgroupWidgets = texelFetch(culledWidgets, ivec2(gl_WorkGroupID.xy), 0).xy;
uint textWidgets = subgroupWidgets.x;
uint graphWidgets = subgroupWidgets.y;
// Loop through possible graph widgets that can intersect with this block.
while (graphWidgets != 0)
{
const uint graphWidget = findLSB(graphWidgets);
graphWidgets ^= 1 << graphWidget;
const uvec4 widgetCoords = graphWidgetsData[graphWidget].coordinates;
if (!intersects(imageCoords, widgetCoords))
{
continue;
}
if (imageCoords.x == widgetCoords.x || imageCoords.y == widgetCoords.y ||
imageCoords.x + 1 == widgetCoords.z || imageCoords.y + 1 == widgetCoords.w)
{
// Use a black border around the graph to mark the area.
blendedWidgets = vec4(0);
continue;
}
const uvec2 coordInWidget = imageCoords - widgetCoords.xy;
const uint valueWidth = graphWidgetsData[graphWidget].valueWidth.x;
// Find the value corresponding to this pixel.
const uint value = getValue(graphWidget, coordInWidget, valueWidth);
vec4 color = vec4(0);
const uint widgetHeight = widgetCoords.w - widgetCoords.y;
// If the graph value overflows the designated area, have the last four rows show a
// checkerboard pattern to signify that there is an overflow.
bool indicateOverflow = value > widgetHeight && coordInWidget.y + 4 >= widgetHeight
&& ((coordInWidget.x ^ coordInWidget.y) & 1) == 0;
if ((widgetHeight - coordInWidget.y) < value && !indicateOverflow)
{
color = graphWidgetsData[graphWidget].color;
blendedWidgets = blend(blendedWidgets, color);
}
}
// Loop through possible text widgets that can intersect with this block.
while (textWidgets != 0)
{
const uint textWidget = findLSB(textWidgets);
textWidgets ^= 1 << textWidget;
const uvec4 widgetCoords = textWidgetsData[textWidget].coordinates;
if (!intersects(imageCoords, widgetCoords))
{
continue;
}
const uvec2 coordInWidget = imageCoords - widgetCoords.xy;
const uvec4 fontSizePacked = textWidgetsData[textWidget].fontSize;
const uvec2 fontGlyphSize = fontSizePacked.xy;
const uint fontLayer = fontSizePacked.z;
// Find the character corresponding to this pixel.
const uint textChar = getChar(textWidget, coordInWidget, fontGlyphSize.x);
// The FONT_CHARACTERS value is a value filled where there is no character, so we don't add
// a background to it.
if (textChar < FONT_CHARACTERS)
{
// Sample the font based on this character.
const float sampleValue = sampleFont(textChar, coordInWidget, fontGlyphSize, fontLayer);
vec4 color = vec4(0, 0, 0, 0.4);
color = mix(color, textWidgetsData[textWidget].color, sampleValue);
blendedWidgets = blend(blendedWidgets, color);
}
}
if (blendedWidgets.a < 1)
{
vec3 blendedColor = blendedWidgets.rgb;
if (blendedWidgets.a > 0)
{
const vec4 color = imageLoad(blendOutput, ivec2(imageCoords));
blendedColor += color.rgb * color.a * blendedWidgets.a;
}
imageStore(blendOutput, ivec2(imageCoords), vec4(blendedColor, 1));
}
}