Added text support and multi-pipeline UI renderer

This commit is contained in:
Alexandro Sánchez Bach 2016-02-04 20:29:58 +01:00
parent 2f1372edca
commit 5780869b12
27 changed files with 467 additions and 227 deletions

View file

@ -65,7 +65,14 @@ D3D12_COMPARISON_FUNC convertComparisonFunc(gfx::ComparisonFunc comparisonFunc)
D3D12_FILTER convertFilter(gfx::Filter filter) {
switch (filter) {
case FILTER_MIN_MAG_MIP_POINT: return D3D12_FILTER_MIN_MAG_MIP_POINT;
case FILTER_MIN_MAG_MIP_POINT: return D3D12_FILTER_MIN_MAG_MIP_POINT;
case FILTER_MIN_MAG_POINT_MIP_LINEAR: return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR;
case FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT: return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
case FILTER_MIN_POINT_MAG_MIP_LINEAR: return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR;
case FILTER_MIN_LINEAR_MAG_MIP_POINT: return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT;
case FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR: return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
case FILTER_MIN_MAG_LINEAR_MIP_POINT: return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
case FILTER_MIN_MAG_MIP_LINEAR: return D3D12_FILTER_MIN_MAG_MIP_LINEAR;
default:
assert_always("Unimplemented case");
return D3D12_FILTER_MIN_MAG_MIP_POINT;
@ -74,10 +81,12 @@ D3D12_FILTER convertFilter(gfx::Filter filter) {
DXGI_FORMAT convertFormat(gfx::Format format) {
switch (format) {
case FORMAT_R32G32: return DXGI_FORMAT_R32G32_FLOAT;
case FORMAT_R32G32B32: return DXGI_FORMAT_R32G32B32_FLOAT;
case FORMAT_R32G32B32A32: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case FORMAT_R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
case FORMAT_R32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
case FORMAT_R32G32_FLOAT: return DXGI_FORMAT_R32G32_FLOAT;
case FORMAT_R32G32B32_FLOAT: return DXGI_FORMAT_R32G32B32_FLOAT;
case FORMAT_R32G32B32A32_FLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case FORMAT_R8_UNORM: return DXGI_FORMAT_R8_UNORM;
case FORMAT_R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
default:
assert_always("Unimplemented case");
return DXGI_FORMAT_UNKNOWN;

View file

@ -267,12 +267,11 @@ std::string Direct3D12Shader::getPointer(Literal pointerId) {
return idCache[pointerId];
}
std::string pointerString;
Instruction* pointerInstr = module->idInstructions[pointerId];
Instruction* typeInstr = module->idInstructions[pointerInstr->typeId];
assert(typeInstr->opcode == OP_TYPE_POINTER);
std::string pointerStr;
std::string pointerString;
switch (typeInstr->operands[0]) {
case StorageClass::UNIFORM_CONSTANT:
pointerString = "uniform."; break;
@ -280,6 +279,8 @@ std::string Direct3D12Shader::getPointer(Literal pointerId) {
pointerString = "input."; break;
case StorageClass::OUTPUT:
pointerString = "output."; break;
case StorageClass::FUNCTION:
break;
default:
assert_always("Unimplemented");
}
@ -289,9 +290,17 @@ std::string Direct3D12Shader::getPointer(Literal pointerId) {
}
if (pointerInstr->opcode == OP_ACCESS_CHAIN) {
pointerString += format("v%d", pointerInstr->operands[0]);
for (size_t i = 1; i < pointerInstr->operands.size(); i++) {
std::string constant = getConstant(pointerInstr->operands[i]);
pointerString += format(".m%s", constant.c_str());
Instruction* varType = module->idInstructions[pointerInstr->operands[0]];
Instruction* ptrType = module->idInstructions[varType->typeId];
Instruction* underlyingType = module->idInstructions[ptrType->operands[1]];
if (underlyingType->opcode == OP_TYPE_STRUCT) {
for (size_t i = 1; i < pointerInstr->operands.size(); i++) {
std::string constant = getConstant(pointerInstr->operands[i]);
pointerString += format(".m%s", constant.c_str());
}
} else if (underlyingType->opcode == OP_TYPE_VECTOR) {
std::string constant = getConstant(pointerInstr->operands[1]);
pointerString += format("[%s]", constant.c_str());
}
}
@ -380,6 +389,21 @@ std::string Direct3D12Shader::emitOpStore(Instruction* i) {
return format(PADDING "%s = v%d;\n", pointerStr.c_str(), object);
}
std::string Direct3D12Shader::emitOpVariable(hir::Instruction* i) {
assert_true(i->operands.size() >= 1);
assert_true(i->operands[0] == StorageClass::FUNCTION);
Literal result = i->resultId;
std::string type = getType(i->typeId);
return format(PADDING "%s v%d;\n", type.c_str(), result);
}
std::string Direct3D12Shader::emitOpVectorShuffle(hir::Instruction* i) {
assert_true(i->operands.size() >= 2);
assert_always("Unimplemented");
return "";
}
std::string Direct3D12Shader::compile(Instruction* i) {
std::string source;
switch (i->opcode) {
@ -411,12 +435,16 @@ std::string Direct3D12Shader::compile(Instruction* i) {
return emitBinaryOp(i, OP_TYPE_INT, '%'); // TODO: Is this correct?
case OP_SREM:
return emitBinaryOp(i, OP_TYPE_INT, '%'); // TODO: Is this correct?
case OP_VECTOR_SHUFFLE:
return emitOpVectorShuffle(i);
case OP_COMPOSITE_EXTRACT:
return emitOpCompositeExtract(i);
case OP_COMPOSITE_CONSTRUCT:
return emitOpCompositeConstruct(i);
case OP_IMAGE_SAMPLE_IMPLICIT_LOD:
return emitOpImageSample(i);
case OP_VARIABLE:
return emitOpVariable(i);
case OP_LOAD:
return emitOpLoad(i);
case OP_STORE:

View file

@ -64,6 +64,8 @@ class Direct3D12Shader : public Shader {
std::string emitOpImageSample(hir::Instruction* i);
std::string emitOpLoad(hir::Instruction* i);
std::string emitOpStore(hir::Instruction* i);
std::string emitOpVariable(hir::Instruction* i);
std::string emitOpVectorShuffle(hir::Instruction* i);
// Compile HIR components into HLSL
std::string compile(hir::Instruction* i);

View file

@ -8,9 +8,11 @@
namespace gfx {
const FormatInfo formatInfo[_FORMAT_COUNT] = {
{ FORMAT_R32G32, 8 },
{ FORMAT_R32G32B32, 12 },
{ FORMAT_R32G32B32A32, 16 },
{ FORMAT_R32_FLOAT, 4 },
{ FORMAT_R32G32_FLOAT, 8 },
{ FORMAT_R32G32B32_FLOAT, 12 },
{ FORMAT_R32G32B32A32_FLOAT, 16 },
{ FORMAT_R8_UNORM, 1},
{ FORMAT_R8G8B8A8_UNORM, 4 },
};

View file

@ -10,9 +10,11 @@
namespace gfx {
enum Format {
FORMAT_R32G32,
FORMAT_R32G32B32,
FORMAT_R32G32B32A32,
FORMAT_R32_FLOAT,
FORMAT_R32G32_FLOAT,
FORMAT_R32G32B32_FLOAT,
FORMAT_R32G32B32A32_FLOAT,
FORMAT_R8_UNORM,
FORMAT_R8G8B8A8_UNORM,
_FORMAT_COUNT,

View file

@ -27,6 +27,7 @@ enum StorageClass {
UNIFORM_CONSTANT = 0,
INPUT = 1,
OUTPUT = 3,
FUNCTION = 7,
};
enum Dimension {

View file

@ -22,7 +22,14 @@ enum ComparisonFunc {
};
enum Filter {
FILTER_MIN_MAG_MIP_POINT,
FILTER_MIN_MAG_MIP_POINT = 0,
FILTER_MIN_MAG_POINT_MIP_LINEAR = 0x1,
FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x4,
FILTER_MIN_POINT_MAG_MIP_LINEAR = 0x5,
FILTER_MIN_LINEAR_MAG_MIP_POINT = 0x10,
FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x11,
FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14,
FILTER_MIN_MAG_MIP_LINEAR = 0x15,
};
enum TextureAddress {

View file

@ -26,35 +26,32 @@ ScreenLogo::ScreenLogo(UI* manager) : Screen(manager) {
logo->style.width = 50_pct;
logo->style.background = Color{1,0,1,1};
/*auto* version = new WidgetText("version");
auto* version = new WidgetText("version");
version->manager = manager;
version->update(defaultFont, 12_px, "Version v0.1.0 from 2016-04-01");
version->style.margin.top = 10_px;
version->style.width = 240_px;
version->style.height = 20_px;
version->style.background = Color{1,1,0,1};
version->update(defaultFont, 30_px, "Version v0.1.0 from 2016-04-01");
version->style.margin.top = 0_px;
version->style.width = 25_pct;
version->style.color = Color{1,1,1,1};
auto* author = new WidgetText("author");
author->manager = manager;
author->update(defaultFont, 20_px, "Created by Alexandro Sanchez Bach");
author->update(defaultFont, 60_px, "Created by Alexandro Sanchez Bach");
author->style.margin.top = 40_px;
author->style.width = 400_px;
author->style.height = 30_px;
author->style.background = Color{1,1,0,1};
author->style.width = 40_pct;
author->style.color = Color{1,1,1,1};
auto* license = new WidgetText("license");
license->manager = manager;
license->update(defaultFont, 20_px, "Licensed under GPL v2.0");
license->style.margin.top = 10_px;
license->style.width = 300_px;
license->style.height = 30_px;
license->style.background = Color{1,1,0,1};*/
license->update(defaultFont, 60_px, "Licensed under GPL v2.0");
license->style.margin.top = 0_px;
license->style.width = 30_pct;
license->style.color = Color{1,1,1,1};
// Add widgets
body.addElement(logo);
/*body.addElement(version);
body.addElement(version);
body.addElement(author);
body.addElement(license);*/
body.addElement(license);
}
void ScreenLogo::update() {

View file

@ -18,6 +18,31 @@ ScreenMain::ScreenMain(UI* manager) : Screen(manager) {
auto* content = new WidgetContainer("content");
content->style.padding = 100_px;
// content->home (@from=header)
// content->home->current
// content->home->current->window1..windowN
// content->home->recent
// content->home->recent->app1..appN
// content->home->platforms
// content->home->platforms->platform1..platformN
// content->platform (@from=content->home->platforms, dynamic)
// content->platform->details
// content->platform->apps
// content->platform->apps->app1..appN
// content->profile (@from=header)
// content->profile->details
// content->profile->messages?
// content->profile->achievements?
// content->profile->friends?
// content->settings (@from=header)
// content->settings->?
// content->help (@from=header)
// content->help->?
body.addElement(header);
body.addElement(content);
}

View file

@ -21,13 +21,6 @@ enum AlignVertical {
ALIGN_VERTICAL_BOTTOM,
};
enum ProportionMode {
PROPORTION_FIXED, // Use the provided width/height dimensions
PROPORTION_AUTOWIDTH, // Calculate width based on the given height
PROPORTION_AUTOHEIGHT, // Calculate height based on the given width
PROPORTION_AUTO, // Calculate width and height
};
struct Color {
float r;
float g;
@ -35,6 +28,22 @@ struct Color {
float a;
};
/**
* Style
* =====
* ## Box Model
* There are four nested boxes: Margin > Border > Padding > Content.
* - Content: The actual contents of the object,
* - Padding: Clears a transparent area around the content.
* - Border: Border that goes around the padding.
* - Margin: Clears a transparent area around the border.
*
* ## Z-Index
* Depth index to render an object with. Greater values imply nearer objects and
* lower values imply farther objects. This is used to compute object occlusion.
* - Valid values are 1 to 255.
* - Specify 0 to automatically assign Z-Index based on the object render tree position.
*/
class Style {
private:
struct Box {
@ -63,18 +72,13 @@ public:
Box padding;
Box margin;
struct Border {
Length top;
Length right;
Length bottom;
Length left;
struct Border : Box {
Color color;
} border;
Color color;
Color background;
ProportionMode sizeMode = PROPORTION_AUTO;
Size zindex = 0;
float opacity = 1.0;
};

View file

@ -6,7 +6,6 @@
#include "ui.h"
#include "nucleus/core/config.h"
#include "nucleus/core/resource.h"
#include "nucleus/graphics/frontend/shader_parser.h"
#include "nucleus/ui/screens/list.h"
#include "nucleus/ui/widgets/list.h"
@ -37,48 +36,13 @@ void UI::task() {
const float clearColor[] = {0.157f, 0.157f, 0.360f, 1.0f};
cmdBuffer = graphics->createCommandBuffer();
gfx::ShaderDesc vertDesc = {};
gfx::ShaderDesc fragDesc = {};
core::Resource resVS(core::RES_SHADER_UI_WIDGET_VS);
core::Resource resPS(core::RES_SHADER_UI_WIDGET_PS);
vertDesc.type = gfx::SHADER_TYPE_VERTEX;
vertDesc.module = gfx::frontend::ShaderParser::parse(reinterpret_cast<const char*>(resVS.data), resVS.size);
fragDesc.type = gfx::SHADER_TYPE_PIXEL;
fragDesc.module = gfx::frontend::ShaderParser::parse(reinterpret_cast<const char*>(resPS.data), resPS.size);
auto* vertShader = graphics->createShader(vertDesc);
auto* fragShader = graphics->createShader(fragDesc);
gfx::PipelineDesc pipelineDesc = {};
pipelineDesc.vs = vertShader;
pipelineDesc.ps = fragShader;
pipelineDesc.iaState.topology = gfx::TOPOLOGY_TRIANGLE_STRIP;
pipelineDesc.iaState.inputLayout = {
{ 0, gfx::FORMAT_R32G32, 0, 0, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 },
{ 1, gfx::FORMAT_R32G32B32A32, 0, 8, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 },
{ 2, gfx::FORMAT_R32G32, 0, 24, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 },
};
pipelineDesc.cbState.colorTarget[0] = {
true, false,
gfx::BLEND_SRC_ALPHA, gfx::BLEND_INV_SRC_ALPHA, gfx::BLEND_OP_ADD,
gfx::BLEND_SRC_ALPHA, gfx::BLEND_INV_SRC_ALPHA, gfx::BLEND_OP_ADD,
gfx::LOGIC_OP_NOOP,
gfx::COLOR_WRITE_ENABLE_ALL
};
pipelineDesc.samplers.resize(1);
pipelineDesc.samplers[0] = {
gfx::FILTER_MIN_MAG_MIP_POINT,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
};
gfx::Pipeline* pipeline = graphics->createPipeline(pipelineDesc);
pipelineContainers = WidgetContainer::createPipeline(*graphics);
pipelineImages = WidgetImage::createPipeline(*graphics);
pipelineText = WidgetText::createPipeline(*graphics);
// Initial screen
#if defined(NUCLEUS_PLATFORM_UWP)
screens.push_back(std::make_unique<ScreenMain>(this));
screens.push_back(std::make_unique<ScreenLogo>(this));
#else
screens.push_back(std::make_unique<ScreenLogo>(this));
#endif
@ -89,7 +53,6 @@ void UI::task() {
const gfx::Rectangle scissor = { 0, 0, surface.getWidth(), surface.getHeight() };
cmdBuffer->reset();
cmdBuffer->cmdBindPipeline(pipeline);
gfx::ResourceBarrier barrierBegin;
barrierBegin.transition.resource = graphics->screenBackBuffer;
@ -102,10 +65,8 @@ void UI::task() {
cmdBuffer->cmdSetScissors(1, &scissor);
cmdBuffer->cmdClearColor(graphics->screenBackTarget, clearColor);
// Vertex buffer
widgetVtxBuffer.clear();
// Display screens
// Re-fill vertex buffers
clearVtxBuffers();
for (auto i = 0ULL; i < screens.size(); i++) {
auto& screen = screens[i];
screen->prologue();
@ -117,25 +78,10 @@ void UI::task() {
screens.erase(screens.begin() + i--);
}
}
// Render widgets
if (!widgetVtxBuffer.empty()) {
gfx::VertexBufferDesc vtxBufferDesc = {};
vtxBufferDesc.size = widgetVtxBuffer.size() * sizeof(WidgetInput);
gfx::VertexBuffer* vtxBuffer = graphics->createVertexBuffer(vtxBufferDesc);
void* bufferAddr = vtxBuffer->map();
memcpy(bufferAddr, widgetVtxBuffer.data(), vtxBufferDesc.size);
vtxBuffer->unmap();
U32 offsets[] = {0};
U32 strides[] = { sizeof(WidgetInput) / 4 };
cmdBuffer->cmdSetPrimitiveTopology(gfx::TOPOLOGY_TRIANGLE_STRIP);
cmdBuffer->cmdSetVertexBuffers(0, 1, &vtxBuffer, offsets, strides);
for (size_t i = 0; i < widgetVtxBuffer.size(); i++) {
cmdBuffer->cmdDraw(4 * i, 4, 0, 1);
}
}
renderContainers();
renderImages();
renderText();
// Add new screens
while (!newScreens.empty()) {
@ -163,12 +109,104 @@ void UI::pushScreen(std::unique_ptr<Screen>&& screen) {
}
// Rendering
void UI::renderWidget(const WidgetInput& input) {
widgetVtxBuffer.push_back(input);
void UI::clearVtxBuffers() {
dataContainers.clear();
dataImages.clear();
dataText.clear();
}
void UI::bindImage(const gfx::Texture* texture) {
cmdBuffer->cmdSetTexture(0, texture);
void UI::renderContainers() {
if (dataContainers.empty()) {
return;
}
cmdBuffer->cmdBindPipeline(pipelineContainers);
gfx::VertexBufferDesc vtxBufferDesc = {};
vtxBufferDesc.size = dataContainers.size() * sizeof(WidgetContainerInput);
auto* vtxBuffer = graphics->createVertexBuffer(vtxBufferDesc);
vtxBufferContainers.reset(vtxBuffer);
void* bufferAddr = vtxBuffer->map();
memcpy(bufferAddr, dataContainers.data(), vtxBufferDesc.size);
vtxBuffer->unmap();
U32 offsets[] = { 0 };
U32 strides[] = { sizeof(WidgetContainerInput) / 4 };
cmdBuffer->cmdSetPrimitiveTopology(gfx::TOPOLOGY_TRIANGLE_STRIP);
cmdBuffer->cmdSetVertexBuffers(0, 1, &vtxBuffer, offsets, strides);
for (size_t i = 0; i < dataContainers.size(); i++) {
cmdBuffer->cmdDraw(4 * i, 4, 0, 1);
}
}
void UI::renderImages() {
if (dataImages.empty()) {
return;
}
cmdBuffer->cmdBindPipeline(pipelineImages);
gfx::VertexBufferDesc vtxBufferDesc = {};
vtxBufferDesc.size = dataImages.size() * sizeof(WidgetImageInput);
auto* vtxBuffer = graphics->createVertexBuffer(vtxBufferDesc);
vtxBufferImages.reset(vtxBuffer);
void* bufferAddr = vtxBuffer->map();
memcpy(bufferAddr, dataImages.data(), vtxBufferDesc.size);
vtxBuffer->unmap();
U32 offsets[] = { 0 };
U32 strides[] = { sizeof(WidgetImageInput) / 4 };
cmdBuffer->cmdSetPrimitiveTopology(gfx::TOPOLOGY_TRIANGLE_STRIP);
cmdBuffer->cmdSetVertexBuffers(0, 1, &vtxBuffer, offsets, strides);
gfx::Texture* texture = nullptr;
for (size_t i = 0; i < dataImages.size(); i++) {
if (texture != textureImages[i]) {
texture = textureImages[i];
cmdBuffer->cmdSetTexture(0, texture);
}
cmdBuffer->cmdDraw(4 * i, 4, 0, 1);
}
}
void UI::renderText() {
if (dataText.empty()) {
return;
}
cmdBuffer->cmdBindPipeline(pipelineText);
gfx::VertexBufferDesc vtxBufferDesc = {};
vtxBufferDesc.size = dataText.size() * sizeof(WidgetTextInput);
auto* vtxBuffer = graphics->createVertexBuffer(vtxBufferDesc);
vtxBufferText.reset(vtxBuffer);
void* bufferAddr = vtxBuffer->map();
memcpy(bufferAddr, dataText.data(), vtxBufferDesc.size);
vtxBuffer->unmap();
U32 offsets[] = { 0 };
U32 strides[] = { sizeof(WidgetTextInput) / 4 };
cmdBuffer->cmdSetPrimitiveTopology(gfx::TOPOLOGY_TRIANGLE_STRIP);
cmdBuffer->cmdSetVertexBuffers(0, 1, &vtxBuffer, offsets, strides);
gfx::Texture* texture = nullptr;
for (size_t i = 0; i < dataText.size(); i++) {
if (texture != textureText[i]) {
texture = textureText[i];
cmdBuffer->cmdSetTexture(0, texture);
}
cmdBuffer->cmdDraw(4 * i, 4, 0, 1);
}
}
void UI::pushWidgetContainer(const WidgetContainerInput& input) {
dataContainers.push_back(input);
}
void UI::pushWidgetImage(const WidgetImageInput& input, gfx::Texture* texture) {
dataImages.push_back(input);
textureImages.push_back(texture);
}
void UI::pushWidgetText(const WidgetTextInput& input, gfx::Texture* texture) {
dataText.push_back(input);
textureText.push_back(texture);
}
} // namespace ui

View file

@ -58,23 +58,50 @@ public:
void pushScreen(std::unique_ptr<Screen>&& screen);
public:
// Rendering methods
std::vector<WidgetInput> widgetVtxBuffer;
// Pipelines
gfx::Pipeline* pipelineContainers;
gfx::Pipeline* pipelineImages;
gfx::Pipeline* pipelineText;
/**
* Pushes a new screen to the back of the screen array
* @param[in] screen Widget vertices to include in the buffer
*/
void renderWidget(const WidgetInput& input);
// Inputs
std::vector<WidgetContainerInput> dataContainers;
std::vector<WidgetImageInput> dataImages;
std::vector<WidgetTextInput> dataText;
std::vector<gfx::Texture*> textureImages;
std::vector<gfx::Texture*> textureText;
// Vertex buffers
std::unique_ptr<gfx::VertexBuffer> vtxBufferContainers;
std::unique_ptr<gfx::VertexBuffer> vtxBufferImages;
std::unique_ptr<gfx::VertexBuffer> vtxBufferText;
// Clears all UI-related vertex and texture buffers
void clearVtxBuffers();
// Rendering methods
void renderContainers();
void renderImages();
void renderText();
/**
* TODO
* Pushes a new WidgetContainer input data to the buffer
* @param[in] input WidgetContainer vertex data
*/
void bindImage(const gfx::Texture* texture);
void pushWidgetContainer(const WidgetContainerInput& input);
/**
* Pushes a new WidgetImage input data to the buffer
* @param[in] input WidgetImage vertex data
* @param[in] texture WidgetImage texture
*/
void pushWidgetImage(const WidgetImageInput& input, gfx::Texture* texture);
/**
* Pushes a new WidgetText input data to the buffer
* @param[in] input WidgetText vertex data
* @param[in] texture WidgetText texture
*/
void pushWidgetText(const WidgetTextInput& input, gfx::Texture* texture);
};
} // namespace ui

View file

@ -19,11 +19,42 @@ namespace ui {
// Forward declarations
class UI;
// Single-pipeline inputs
struct WidgetInput {
struct WidgetVertex {
struct Vertex {
float position[2];
float texcoord[2];
Color color0;
float type;
float zindex;
float opacity;
};
};
// Multi-pipeline inputs
struct WidgetContainerInput {
struct Vertex {
float position[2];
Color background;
float zindex;
float opacity;
} vertex[4];
};
struct WidgetImageInput {
struct Vertex {
float position[2];
float texcoord[2];
float zindex;
float opacity;
} vertex[4];
};
struct WidgetTextInput {
struct Vertex {
float position[2];
float texcoord[2];
Color color;
float zindex;
float opacity;
} vertex[4];
};
@ -48,15 +79,29 @@ struct WidgetInput {
* | Padding | getPaddingWidth() | getPaddingHeight() |
* | Border | getBorderWidth() | getBorderHeight() |
* | Margin | getMarginWidth() | getMarginHeight() |
*
* ## Input
* The vertex data input for any Widget consists at least of four vertices (V0, V1, V2, V3)
* each provided with a 2D position attribute such that a rectangle is formed.
* The standard coordinate system for position-like attributes goes from 0 to 1 in both axis
* with the origin starting in the bottom-left corner of the screen.
*
* (0,1) (1,1)
* +--------------------------------------+
* | V1 = (x1,y2) V3 = (x2,y2) |
* | + - - - - - - - + |
* | : \ : |
* | : \ : |
* | : \ : h |
* | : \ : |
* | : w \ : |
* | + - - - - - - - + |
* | V0 = (x1,y1) V2 = (x2,y1) |
* +--------------------------------------+
* (0,0) (1,0)
*/
class Widget {
protected:
// Texture produced after rendering the contents of this widget
gfx::Texture* texture;
// Vertices to be rendered
WidgetInput input;
// Final dimension and size
float vertWidth;
float vertHeight;
@ -67,6 +112,7 @@ protected:
float compWidth;
float compHeight;
// Transform lengths to our standard coordinate system
float getCoord(const Length& length, float pixels);
float getCoordX(const Length& length);
float getCoordY(const Length& length);

View file

@ -5,12 +5,44 @@
#include "widget_container.h"
#include "nucleus/assert.h"
#include "nucleus/core/resource.h"
#include "nucleus/graphics/frontend/shader_parser.h"
#include "nucleus/ui/ui.h"
#include <algorithm>
namespace ui {
gfx::Pipeline* WidgetContainer::createPipeline(gfx::IBackend& backend) {
gfx::ShaderDesc vertDesc = {};
gfx::ShaderDesc fragDesc = {};
core::Resource resVS(core::RES_SHADER_UI_WIDGET_CONTAINER_VS);
core::Resource resPS(core::RES_SHADER_UI_WIDGET_CONTAINER_PS);
vertDesc.type = gfx::SHADER_TYPE_VERTEX;
vertDesc.module = gfx::frontend::ShaderParser::parse(reinterpret_cast<const char*>(resVS.data), resVS.size);
fragDesc.type = gfx::SHADER_TYPE_PIXEL;
fragDesc.module = gfx::frontend::ShaderParser::parse(reinterpret_cast<const char*>(resPS.data), resPS.size);
gfx::PipelineDesc pipelineDesc = {};
pipelineDesc.vs = backend.createShader(vertDesc);
pipelineDesc.ps = backend.createShader(fragDesc);
pipelineDesc.iaState.topology = gfx::TOPOLOGY_TRIANGLE_STRIP;
pipelineDesc.iaState.inputLayout = {
{ 0, gfx::FORMAT_R32G32_FLOAT, 0, 0, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 }, // Position
{ 1, gfx::FORMAT_R32G32B32A32_FLOAT, 0, 8, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 }, // Color
{ 2, gfx::FORMAT_R32_FLOAT, 0, 24, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 }, // Z-Index
{ 3, gfx::FORMAT_R32_FLOAT, 0, 28, 0, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 }, // Opacity
};
pipelineDesc.cbState.colorTarget[0] = {
true, false,
gfx::BLEND_SRC_ALPHA, gfx::BLEND_INV_SRC_ALPHA, gfx::BLEND_OP_ADD,
gfx::BLEND_SRC_ALPHA, gfx::BLEND_INV_SRC_ALPHA, gfx::BLEND_OP_ADD,
gfx::LOGIC_OP_NOOP,
gfx::COLOR_WRITE_ENABLE_ALL
};
return backend.createPipeline(pipelineDesc);
}
bool WidgetContainer::addElement(Widget* widget) {
assert_true(widget->parent == nullptr, "Widget already has a parent");
widget->parent = this;
@ -97,23 +129,6 @@ void WidgetContainer::render() {
auto y1 = +1.0 - 2 * (offsetTop + getPaddingHeight());
auto y2 = +1.0 - 2 * (offsetTop);
/**
* Screen
* ======
* (0,1) (1,1)
* +--------------------------------------+
* | V1 = (x1,y2) V3 = (x2,y2) |
* | + - - - - - - - + |
* | : \ : |
* | : \ : |
* | : \ : h |
* | : \ : |
* | : w \ : |
* | + - - - - - - - + |
* | V0 = (x1,y1) V2 = (x2,y1) |
* +--------------------------------------+
* (0,0) (1,0)
*/
auto& V0 = input.vertex[0];
auto& V1 = input.vertex[1];
auto& V2 = input.vertex[2];
@ -130,7 +145,7 @@ void WidgetContainer::render() {
V2.background = style.background;
V3.background = style.background;
manager->renderWidget(input);
manager->pushWidgetContainer(input);
auto childOffsetTop = offsetTop + getCoordY(style.padding.top);
auto childOffsetLeft = offsetLeft + getCoordX(style.padding.left);

View file

@ -12,6 +12,10 @@
namespace ui {
class WidgetContainer : public Widget {
private:
// Input
WidgetContainerInput input;
// Elements of the widget
std::vector<Widget*> children;

View file

@ -18,7 +18,7 @@ WidgetImage::~WidgetImage() {
stbi_image_free(imBuffer);
}
gfx::Pipeline* WidgetContainer::createPipeline(gfx::IBackend& backend) {
gfx::Pipeline* WidgetImage::createPipeline(gfx::IBackend& backend) {
gfx::ShaderDesc vertDesc = {};
gfx::ShaderDesc fragDesc = {};
core::Resource resVS(core::RES_SHADER_UI_WIDGET_IMAGE_VS);
@ -47,7 +47,7 @@ gfx::Pipeline* WidgetContainer::createPipeline(gfx::IBackend& backend) {
};
pipelineDesc.samplers.resize(1);
pipelineDesc.samplers[0] = {
gfx::FILTER_MIN_MAG_MIP_POINT,
gfx::FILTER_MIN_MAG_MIP_LINEAR,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
@ -128,23 +128,6 @@ void WidgetImage::render() {
auto y1 = +1.0 - 2 * (offsetTop + getPaddingHeight());
auto y2 = +1.0 - 2 * (offsetTop);
/**
* Screen
* ======
* (0,1) (1,1)
* +--------------------------------------+
* | V1 = (x1,y2) V3 = (x2,y2) |
* | + - - - - - - - + |
* | : \ : |
* | : \ : |
* | : \ : h |
* | : \ : |
* | : w \ : |
* | + - - - - - - - + |
* | V0 = (x1,y1) V2 = (x2,y1) |
* +--------------------------------------+
* (0,0) (1,0)
*/
auto& V0 = input.vertex[0];
auto& V1 = input.vertex[1];
auto& V2 = input.vertex[2];
@ -157,19 +140,13 @@ void WidgetImage::render() {
V0.position[2] = V1.position[2] = V2.position[2] = V3.position[2] = 0.0;
V0.position[3] = V1.position[3] = V2.position[3] = V3.position[3] = 1.0;
V0.background = style.background;
V1.background = style.background;
V2.background = style.background;
V3.background = style.background;
// Texture coordinates assume top-left is (0,0) and bottom-right is (1,1)
V0.texcoord[0] = 0.0; V0.texcoord[1] = 1.0;
V1.texcoord[0] = 0.0; V1.texcoord[1] = 0.0;
V2.texcoord[0] = 1.0; V2.texcoord[1] = 1.0;
V3.texcoord[0] = 1.0; V3.texcoord[1] = 0.0;
manager->bindImage(texture);
manager->renderWidget(input);
manager->pushWidgetImage(input, texture);
}
} // namespace ui

View file

@ -14,6 +14,11 @@
namespace ui {
class WidgetImage : public Widget {
private:
// Input
WidgetImageInput input;
gfx::Texture* texture;
// Image
unsigned char* imBuffer;
int imWidth = 0;

View file

@ -13,7 +13,7 @@
namespace ui {
gfx::Pipeline* WidgetContainer::createPipeline(gfx::IBackend& backend) {
gfx::Pipeline* WidgetText::createPipeline(gfx::IBackend& backend) {
gfx::ShaderDesc vertDesc = {};
gfx::ShaderDesc fragDesc = {};
core::Resource resVS(core::RES_SHADER_UI_WIDGET_TEXT_VS);
@ -43,7 +43,7 @@ gfx::Pipeline* WidgetContainer::createPipeline(gfx::IBackend& backend) {
};
pipelineDesc.samplers.resize(1);
pipelineDesc.samplers[0] = {
gfx::FILTER_MIN_MAG_MIP_POINT,
gfx::FILTER_MIN_MAG_MIP_LINEAR,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
gfx::TEXTURE_ADDRESS_MIRROR,
@ -73,9 +73,11 @@ void WidgetText::update(const Font* fontFamily, const Length& fontSize, const st
txWidth += (dx + kern) * scale;
}
// Render characters
Size x = 0;
Size y = 0;
// Render characters leaving a 1-pixel margin
Size x = 1;
Size y = 1;
txWidth += 2;
txHeight += 2;
txBuffer.clear();
txBuffer.resize(txWidth * txHeight);
for (Size i = 0; i < text.length(); i++) {
@ -97,7 +99,7 @@ void WidgetText::update(const Font* fontFamily, const Length& fontSize, const st
textureDesc.width = txWidth;
textureDesc.height = txHeight;
textureDesc.mipmapLevels = 1;
textureDesc.format = gfx::FORMAT_R8G8B8A8_UNORM;
textureDesc.format = gfx::FORMAT_R8_UNORM;
textureDesc.data = txBuffer.data();
textureDesc.size = txBuffer.size();
@ -105,18 +107,23 @@ void WidgetText::update(const Font* fontFamily, const Length& fontSize, const st
}
void WidgetText::dimensionalize() {
vertWidth = 0.0;
vertHeight = 0.0;
auto compWidth = getCoordX(Length{ double(txWidth), Length::TYPE_PX });
auto compHeight = getCoordY(Length{ double(txHeight), Length::TYPE_PX });
if (style.width.type == Length::TYPE_UNDEFINED) {
vertWidth = getCoordX(Length{ double(txWidth), Length::TYPE_PX });
} else {
if (style.width.type != Length::TYPE_UNDEFINED) {
vertWidth = getCoordX(style.width);
}
if (style.height.type == Length::TYPE_UNDEFINED) {
vertHeight = getCoordY(Length{ double(txHeight), Length::TYPE_PX });
} else if (style.height.type != Length::TYPE_UNDEFINED) {
vertWidth = compWidth * getCoordY(style.height) / compHeight;
} else {
vertWidth = compWidth;
}
if (style.height.type != Length::TYPE_UNDEFINED) {
vertHeight = getCoordY(style.height);
} else if (style.width.type != Length::TYPE_UNDEFINED) {
vertHeight = compHeight * getCoordX(style.width) / compWidth;
} else {
vertHeight = compHeight;
}
}
@ -130,23 +137,6 @@ void WidgetText::render() {
auto y1 = +1.0 - 2 * (offsetTop + getPaddingHeight());
auto y2 = +1.0 - 2 * (offsetTop);
/**
* Screen
* ======
* (0,1) (1,1)
* +--------------------------------------+
* | V1 = (x1,y2) V3 = (x2,y2) |
* | + - - - - - - - + |
* | : \ : |
* | : \ : |
* | : \ : h |
* | : \ : |
* | : w \ : |
* | + - - - - - - - + |
* | V0 = (x1,y1) V2 = (x2,y1) |
* +--------------------------------------+
* (0,0) (1,0)
*/
auto& V0 = input.vertex[0];
auto& V1 = input.vertex[1];
auto& V2 = input.vertex[2];
@ -158,12 +148,18 @@ void WidgetText::render() {
V1.position[1] = V3.position[1] = y2;
V0.position[2] = V1.position[2] = V2.position[2] = V3.position[2] = 0.0;
V0.position[3] = V1.position[3] = V2.position[3] = V3.position[3] = 1.0;
V0.background = style.background;
V1.background = style.background;
V2.background = style.background;
V3.background = style.background;
V0.color = style.color;
V1.color = style.color;
V2.color = style.color;
V3.color = style.color;
manager->renderWidget(input);
// Texture coordinates assume top-left is (0,0) and bottom-right is (1,1)
V0.texcoord[0] = 0.0; V0.texcoord[1] = 1.0;
V1.texcoord[0] = 0.0; V1.texcoord[1] = 0.0;
V2.texcoord[0] = 1.0; V2.texcoord[1] = 1.0;
V3.texcoord[0] = 1.0; V3.texcoord[1] = 0.0;
manager->pushWidgetText(input, texture);
}
} // namespace ui

View file

@ -15,6 +15,11 @@
namespace ui {
class WidgetText : public Widget {
private:
// Input
WidgetTextInput input;
gfx::Texture* texture;
// Text
std::vector<Byte> txBuffer;
Size txWidth = 0;

View file

@ -13,6 +13,6 @@ layout (location = 0) out vec4 result;
layout (binding = 0) uniform sampler2D s;
void main() {
result = texture(s, iTexcoord.xy);
result = texture(s, iTexcoord.xy);
//result *= opacity;
}

View file

@ -20,14 +20,14 @@ layout (binding = 0) uniform sampler2D s;
void main() {
if (iType == TYPE_CONTAINER) {
result = iColor;
result = iColor;
}
else if (iType == TYPE_IMAGE) {
result = texture(s, iTexcoord.xy);
result = texture(s, iTexcoord.xy);
}
else if (iType == TYPE_TEXT) {
result = iColor;
result.w = texture(s, iTexcoord.xy).r;
result = iColor;
result.w = texture(s, iTexcoord.xy).r;
}
//result *= opacity;
}

View file

@ -14,7 +14,10 @@ layout (location = 0) out vec4 result;
layout (binding = 0) uniform sampler2D s;
void main() {
result = iColor;
result.w = texture(s, iTexcoord.xy).x;
float r = iColor.x;
float g = iColor.y;
float b = iColor.z;
float a = texture(s, iTexcoord.xy).x;
result = vec4(r, g, b, a);
//result *= opacity;
}

View file

@ -1,10 +1,10 @@
#version 450
layout (location = 0) in vec2 iPosition;
layout (location = 0) in vec2 iTexcoord;
layout (location = 1) in vec4 iColor;
layout (location = 2) in float iZindex;
layout (location = 3) in float iOpacity;
layout (location = 1) in vec2 iTexcoord;
layout (location = 2) in vec4 iColor;
layout (location = 3) in float iZindex;
layout (location = 4) in float iOpacity;
layout (location = 0) out vec2 oTexcoord;
layout (location = 1) out vec4 oColor;

View file

@ -6,6 +6,7 @@ import re
# Directories
NUCLEUS_DIR = os.path.abspath("..")
NUCLEUS_PROJECT = os.path.join(NUCLEUS_DIR, "nucleus")
NUCLEUS_SHADERS = os.path.join(NUCLEUS_DIR, "resources", "shaders")
NUCLEUS_TESTS = os.path.join(NUCLEUS_DIR, "tests", "unit")
@ -26,10 +27,14 @@ def formatGeneric(codeInput):
# Search and edit files of the Nucleus project
def main():
for path in [NUCLEUS_PROJECT, NUCLEUS_TESTS]:
included = (".c", ".cc", ".cpp", ".h", ".hpp", ".glsl")
excluded = (".l.cpp", ".y.cpp", ".y.hpp")
for path in [NUCLEUS_PROJECT, NUCLEUS_SHADERS, NUCLEUS_TESTS]:
for root, dirs, files in os.walk(path):
for filename in files:
if not filename.endswith((".c",".cc",".cpp",".h",".hpp")):
if not filename.endswith(included):
continue
if filename.endswith(excluded):
continue
# Read and format the code

View file

@ -214,10 +214,38 @@
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="..\..\resources\shaders\ui_widget_container_ps.spv" />
<None Include="..\..\resources\shaders\ui_widget_container_vs.spv" />
<None Include="..\..\resources\shaders\ui_widget_image_ps.spv" />
<None Include="..\..\resources\shaders\ui_widget_image_vs.spv" />
<None Include="..\..\resources\shaders\ui_widget_container_ps.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_container_vs.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_image_ps.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_image_vs.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_ps.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
@ -226,8 +254,22 @@
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_text_ps.spv" />
<None Include="..\..\resources\shaders\ui_widget_text_vs.spv" />
<None Include="..\..\resources\shaders\ui_widget_text_ps.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_text_vs.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="..\..\resources\shaders\ui_widget_vs.spv">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>