2020 lines
65 KiB
C++
2020 lines
65 KiB
C++
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
#include "Render2D.h"
|
|
#include "Font.h"
|
|
#include "FontManager.h"
|
|
#include "FontTextureAtlas.h"
|
|
#include "RotatedRectangle.h"
|
|
#include "SpriteAtlas.h"
|
|
#include "Engine/Core/Math/Matrix3x3.h"
|
|
#include "Engine/Content/Assets/Shader.h"
|
|
#include "Engine/Content/Assets/MaterialBase.h"
|
|
#include "Engine/Content/Content.h"
|
|
#include "Engine/Profiler/Profiler.h"
|
|
#include "Engine/Graphics/GPUContext.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/GPUPipelineState.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Graphics/RenderTargetPool.h"
|
|
#include "Engine/Graphics/DynamicBuffer.h"
|
|
#include "Engine/Graphics/Shaders/GPUShader.h"
|
|
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
|
|
#include "Engine/Animations/AnimationUtils.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Math/Half.h"
|
|
#include "Engine/Core/Math/Math.h"
|
|
#include "Engine/Engine/EngineService.h"
|
|
|
|
#if USE_EDITOR
|
|
#define RENDER2D_CHECK_RENDERING_STATE \
|
|
if (!Render2D::IsRendering()) \
|
|
{ \
|
|
LOG(Error, "Calling Render2D is only valid during rendering."); \
|
|
return; \
|
|
}
|
|
#else
|
|
#define RENDER2D_CHECK_RENDERING_STATE
|
|
#endif
|
|
|
|
#if USE_EDITOR
|
|
#define RENDER2D_INITIAL_VB_CAPACITY (16 * 1024)
|
|
#else
|
|
#define RENDER2D_INITIAL_VB_CAPACITY (4 * 1024)
|
|
#endif
|
|
#define RENDER2D_INITIAL_IB_CAPACITY (1024)
|
|
#define RENDER2D_INITIAL_DRAW_CALL_CAPACITY (512)
|
|
|
|
#define RENDER2D_BLUR_MAX_SAMPLES 64
|
|
|
|
// The format for the blur effect temporary buffer
|
|
#define PS_Blur_Format PixelFormat::R8G8B8A8_UNorm
|
|
|
|
// True if enable downscaling when rendering blur
|
|
const bool DownsampleForBlur = false;
|
|
|
|
PACK_STRUCT(struct Data {
|
|
Matrix ViewProjection;
|
|
});
|
|
|
|
PACK_STRUCT(struct BlurData {
|
|
Float2 InvBufferSize;
|
|
uint32 SampleCount;
|
|
float Dummy0;
|
|
Float4 Bounds;
|
|
Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2];
|
|
});
|
|
|
|
enum class DrawCallType : byte
|
|
{
|
|
FillRect,
|
|
FillRectNoAlpha,
|
|
FillRT,
|
|
FillTexture,
|
|
FillTexturePoint,
|
|
DrawChar,
|
|
DrawCharMaterial,
|
|
Custom,
|
|
Material,
|
|
Blur,
|
|
ClipScissors,
|
|
LineAA,
|
|
|
|
MAX
|
|
};
|
|
|
|
struct Render2DDrawCall
|
|
{
|
|
DrawCallType Type;
|
|
uint32 StartIB;
|
|
uint32 CountIB;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
GPUTextureView* Ptr;
|
|
} AsRT;
|
|
|
|
struct
|
|
{
|
|
GPUTexture* Ptr;
|
|
} AsTexture;
|
|
|
|
struct
|
|
{
|
|
GPUTexture* Tex;
|
|
MaterialBase* Mat;
|
|
} AsChar;
|
|
|
|
struct
|
|
{
|
|
GPUTexture* Tex;
|
|
GPUPipelineState* Pso;
|
|
} AsCustom;
|
|
|
|
struct
|
|
{
|
|
MaterialBase* Mat;
|
|
float Width;
|
|
float Height;
|
|
} AsMaterial;
|
|
|
|
struct
|
|
{
|
|
float Strength;
|
|
float Width;
|
|
float Height;
|
|
float UpperLeftX;
|
|
float UpperLeftY;
|
|
float BottomRightX;
|
|
float BottomRightY;
|
|
} AsBlur;
|
|
|
|
struct
|
|
{
|
|
float X;
|
|
float Y;
|
|
float Width;
|
|
float Height;
|
|
} AsClipScissors;
|
|
};
|
|
};
|
|
|
|
struct Render2DVertex
|
|
{
|
|
Float2 Position;
|
|
Half2 TexCoord;
|
|
Color Color;
|
|
Float2 CustomData;
|
|
RotatedRectangle ClipMask;
|
|
};
|
|
|
|
struct CachedPSO
|
|
{
|
|
bool Inited = false;
|
|
bool UseDepth;
|
|
|
|
GPUPipelineState* PS_Image;
|
|
|
|
GPUPipelineState* PS_ImagePoint;
|
|
|
|
GPUPipelineState* PS_Color;
|
|
GPUPipelineState* PS_Color_NoAlpha;
|
|
|
|
GPUPipelineState* PS_Font;
|
|
|
|
GPUPipelineState* PS_BlurH;
|
|
GPUPipelineState* PS_BlurV;
|
|
GPUPipelineState* PS_Downscale;
|
|
|
|
GPUPipelineState* PS_LineAA;
|
|
|
|
bool Init(GPUShader* shader, bool useDepth);
|
|
void Dispose();
|
|
};
|
|
|
|
// Clip
|
|
struct ClipMask
|
|
{
|
|
RotatedRectangle Mask;
|
|
Rectangle Bounds;
|
|
};
|
|
|
|
Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping | RenderingFeatures::FallbackFonts;
|
|
|
|
namespace
|
|
{
|
|
// Private Stuff
|
|
GPUContext* Context = nullptr;
|
|
GPUTextureView* Output = nullptr;
|
|
GPUTextureView* DepthBuffer = nullptr;
|
|
Viewport View;
|
|
Matrix ViewProjection;
|
|
|
|
// Drawing
|
|
Array<Render2DDrawCall> DrawCalls;
|
|
Array<FontLineCache> Lines;
|
|
Array<Float2> Lines2;
|
|
bool IsScissorsRectEmpty;
|
|
bool IsScissorsRectEnabled;
|
|
|
|
// Transform
|
|
// Note: we use Matrix3x3 instead of Matrix because we use only 2D transformations on CPU side
|
|
// Matrix layout:
|
|
// [ m1, m2, 0 ]
|
|
// [ m3, m4, 0 ]
|
|
// [ t1, t2, 1 ]
|
|
// where 'm' is 2D transformation (scale, shear and rotate), 't' is translation
|
|
Array<Matrix3x3, InlinedAllocation<64>> TransformLayersStack;
|
|
Matrix3x3 TransformCached;
|
|
|
|
Array<ClipMask, InlinedAllocation<64>> ClipLayersStack;
|
|
Array<Color, InlinedAllocation<64>> TintLayersStack;
|
|
|
|
// Shader
|
|
AssetReference<Shader> GUIShader;
|
|
CachedPSO PsoDepth;
|
|
CachedPSO PsoNoDepth;
|
|
CachedPSO* CurrentPso = nullptr;
|
|
DynamicVertexBuffer VB(RENDER2D_INITIAL_VB_CAPACITY, (uint32)sizeof(Render2DVertex), TEXT("Render2D.VB"));
|
|
DynamicIndexBuffer IB(RENDER2D_INITIAL_IB_CAPACITY, sizeof(uint32), TEXT("Render2D.IB"));
|
|
uint32 VBIndex = 0;
|
|
uint32 IBIndex = 0;
|
|
}
|
|
|
|
#define RENDER2D_WRITE_IB_QUAD(indices) \
|
|
indices[0] = VBIndex + 0; \
|
|
indices[1] = VBIndex + 1; \
|
|
indices[2] = VBIndex + 2; \
|
|
indices[3] = VBIndex + 2; \
|
|
indices[4] = VBIndex + 3; \
|
|
indices[5] = VBIndex + 0; \
|
|
IB.Write(indices, sizeof(indices))
|
|
|
|
FORCE_INLINE void ApplyTransform(const Float2& value, Float2& result)
|
|
{
|
|
Matrix3x3::Transform2DPoint(value, TransformCached, result);
|
|
}
|
|
|
|
void ApplyTransform(const Rectangle& value, RotatedRectangle& result)
|
|
{
|
|
const RotatedRectangle rotated(value);
|
|
Matrix3x3::Transform2DPoint(rotated.TopLeft, TransformCached, result.TopLeft);
|
|
Matrix3x3::Transform2DVector(rotated.ExtentX, TransformCached, result.ExtentX);
|
|
Matrix3x3::Transform2DVector(rotated.ExtentY, TransformCached, result.ExtentY);
|
|
}
|
|
|
|
FORCE_INLINE Render2DVertex MakeVertex(const Float2& pos, const Float2& uv, const Color& color)
|
|
{
|
|
Float2 point;
|
|
ApplyTransform(pos, point);
|
|
|
|
return
|
|
{
|
|
point,
|
|
Half2(uv),
|
|
color * TintLayersStack.Peek(),
|
|
{ 0.0f, (float)Render2D::Features },
|
|
ClipLayersStack.Peek().Mask
|
|
};
|
|
}
|
|
|
|
FORCE_INLINE Render2DVertex MakeVertex(const Float2& point, const Float2& uv, const Color& color, const RotatedRectangle& mask, const Float2& customData)
|
|
{
|
|
return
|
|
{
|
|
point,
|
|
Half2(uv),
|
|
color,
|
|
customData,
|
|
mask,
|
|
};
|
|
}
|
|
|
|
FORCE_INLINE Render2DVertex MakeVertex(const Float2& point, const Float2& uv, const Color& color, const RotatedRectangle& mask, const Float2& customData, const Color& tint)
|
|
{
|
|
return
|
|
{
|
|
point,
|
|
Half2(uv),
|
|
color * tint,
|
|
customData,
|
|
mask
|
|
};
|
|
}
|
|
|
|
void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2, const Color& color0, const Color& color1, const Color& color2)
|
|
{
|
|
Render2DVertex tris[3];
|
|
tris[0] = MakeVertex(p0, uv0, color0);
|
|
tris[1] = MakeVertex(p1, uv1, color1);
|
|
tris[2] = MakeVertex(p2, uv2, color2);
|
|
VB.Write(tris, sizeof(tris));
|
|
|
|
uint32 indices[3];
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 1;
|
|
indices[2] = VBIndex + 2;
|
|
IB.Write(indices, sizeof(indices));
|
|
|
|
VBIndex += 3;
|
|
IBIndex += 3;
|
|
}
|
|
|
|
void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color0, const Color& color1, const Color& color2)
|
|
{
|
|
WriteTri(p0, p1, p2, Float2::Zero, Float2::Zero, Float2::Zero, color0, color1, color2);
|
|
}
|
|
|
|
void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2)
|
|
{
|
|
WriteTri(p0, p1, p2, uv0, uv1, uv2, Color::Black, Color::Black, Color::Black);
|
|
}
|
|
|
|
void WriteRect(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4)
|
|
{
|
|
const Float2 uvUpperLeft = Float2::Zero;
|
|
const Float2 uvBottomRight = Float2::One;
|
|
|
|
Render2DVertex quad[4];
|
|
quad[0] = MakeVertex(rect.GetBottomRight(), uvBottomRight, color3);
|
|
quad[1] = MakeVertex(rect.GetBottomLeft(), Float2(uvUpperLeft.X, uvBottomRight.Y), color4);
|
|
quad[2] = MakeVertex(rect.GetUpperLeft(), uvUpperLeft, color1);
|
|
quad[3] = MakeVertex(rect.GetUpperRight(), Float2(uvBottomRight.X, uvUpperLeft.Y), color2);
|
|
VB.Write(quad, sizeof(quad));
|
|
|
|
uint32 indices[6];
|
|
RENDER2D_WRITE_IB_QUAD(indices);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
}
|
|
|
|
void WriteRect(const Rectangle& rect, const Color& color, const Float2& uvUpperLeft, const Float2& uvBottomRight)
|
|
{
|
|
Render2DVertex quad[4];
|
|
quad[0] = MakeVertex(rect.GetBottomRight(), uvBottomRight, color);
|
|
quad[1] = MakeVertex(rect.GetBottomLeft(), Float2(uvUpperLeft.X, uvBottomRight.Y), color);
|
|
quad[2] = MakeVertex(rect.GetUpperLeft(), uvUpperLeft, color);
|
|
quad[3] = MakeVertex(rect.GetUpperRight(), Float2(uvBottomRight.X, uvUpperLeft.Y), color);
|
|
VB.Write(quad, sizeof(quad));
|
|
|
|
uint32 indices[6];
|
|
RENDER2D_WRITE_IB_QUAD(indices);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
}
|
|
|
|
FORCE_INLINE void WriteRect(const Rectangle& rect, const Color& color)
|
|
{
|
|
WriteRect(rect, color, Float2::Zero, Float2::One);
|
|
}
|
|
|
|
void Write9SlicingRect(const Rectangle& rect, const Color& color, const Float4& border, const Float4& borderUVs)
|
|
{
|
|
const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z);
|
|
const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z);
|
|
const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W);
|
|
const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W);
|
|
|
|
const Float2 upperLeftUV(borderUVs.X, borderUVs.Z);
|
|
const Float2 upperRightUV(1.0f - borderUVs.Y, borderUVs.Z);
|
|
const Float2 bottomLeftUV(borderUVs.X, 1.0f - borderUVs.W);
|
|
const Float2 bottomRightUV(1.0f - borderUVs.Y, 1.0f - borderUVs.W);
|
|
|
|
WriteRect(upperLeft, color, Float2::Zero, upperLeftUV);
|
|
WriteRect(upperRight, color, Float2(upperRightUV.X, 0), Float2(1, upperLeftUV.Y));
|
|
WriteRect(bottomLeft, color, Float2(0, bottomLeftUV.Y), Float2(bottomLeftUV.X, 1));
|
|
WriteRect(bottomRight, color, bottomRightUV, Float2::One);
|
|
|
|
WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Float2(upperLeftUV.X, 0), upperRightUV);
|
|
WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Float2(0, upperLeftUV.Y), bottomLeftUV);
|
|
WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Float2(bottomRightUV.X, 1));
|
|
WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Float2(1, bottomRightUV.Y));
|
|
|
|
WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV);
|
|
}
|
|
|
|
void Write9SlicingRect(const Rectangle& rect, const Color& color, const Float4& border, const Float4& borderUVs, const Float2& uvLocation, const Float2& uvSize)
|
|
{
|
|
const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z);
|
|
const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z);
|
|
const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W);
|
|
const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W);
|
|
|
|
const Float2 upperLeftUV = Float2(borderUVs.X, borderUVs.Z) * uvSize + uvLocation;
|
|
const Float2 upperRightUV = Float2(1.0f - borderUVs.Y, borderUVs.Z) * uvSize + uvLocation;
|
|
const Float2 bottomLeftUV = Float2(borderUVs.X, 1.0f - borderUVs.W) * uvSize + uvLocation;
|
|
const Float2 bottomRightUV = Float2(1.0f - borderUVs.Y, 1.0f - borderUVs.W) * uvSize + uvLocation;
|
|
const Float2 uvEnd = uvLocation + uvSize;
|
|
|
|
WriteRect(upperLeft, color, uvLocation, upperLeftUV);
|
|
WriteRect(upperRight, color, Float2(upperRightUV.X, uvLocation.Y), Float2(uvEnd.X, upperLeftUV.Y));
|
|
WriteRect(bottomLeft, color, Float2(uvLocation.X, bottomLeftUV.Y), Float2(bottomLeftUV.X, uvEnd.Y));
|
|
WriteRect(bottomRight, color, bottomRightUV, uvEnd);
|
|
|
|
WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Float2(upperLeftUV.X, uvLocation.Y), upperRightUV);
|
|
WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Float2(uvLocation.X, upperLeftUV.Y), bottomLeftUV);
|
|
WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Float2(bottomRightUV.X, uvEnd.Y));
|
|
WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Float2(uvEnd.X, bottomRightUV.Y));
|
|
|
|
WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV);
|
|
}
|
|
|
|
typedef bool (*CanDrawCallCallback)(const Render2DDrawCall&, const Render2DDrawCall&);
|
|
|
|
bool CanDrawCallCallbackTrue(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool CanDrawCallCallbackFalse(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CanDrawCallCallbackRT(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsRT.Ptr == d2.AsRT.Ptr;
|
|
}
|
|
|
|
bool CanDrawCallCallbackTexture(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsTexture.Ptr == d2.AsTexture.Ptr;
|
|
}
|
|
|
|
bool CanDrawCallCallbackChar(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsChar.Tex == d2.AsChar.Tex;
|
|
}
|
|
|
|
bool CanDrawCallCallbackCharMaterial(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsChar.Tex == d2.AsChar.Tex && d1.AsChar.Mat == d2.AsChar.Mat;
|
|
}
|
|
|
|
bool CanDrawCallCallbackCustom(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsCustom.Tex == d2.AsCustom.Tex && d1.AsCustom.Pso == d2.AsCustom.Pso;
|
|
}
|
|
|
|
bool CanDrawCallCallbackMaterial(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.AsMaterial.Mat == d2.AsMaterial.Mat;
|
|
}
|
|
|
|
// @formatter:off
|
|
CanDrawCallCallback CanDrawCallBatch[] =
|
|
{
|
|
CanDrawCallCallbackTrue, // FillRect,
|
|
CanDrawCallCallbackTrue, // FillRectNoAlpha,
|
|
CanDrawCallCallbackRT, // FillRT,
|
|
CanDrawCallCallbackTexture, // FillTexture,
|
|
CanDrawCallCallbackTexture, // FillTexturePoint,
|
|
CanDrawCallCallbackChar, // DrawChar,
|
|
CanDrawCallCallbackCharMaterial, // DrawCharMaterial,
|
|
CanDrawCallCallbackFalse, // Custom,
|
|
CanDrawCallCallbackMaterial, // Material,
|
|
CanDrawCallCallbackFalse, // Blur,
|
|
CanDrawCallCallbackFalse, // ClipScissors,
|
|
CanDrawCallCallbackTrue, // LineAA,
|
|
};
|
|
static_assert(ARRAY_COUNT(CanDrawCallBatch) == (int32)DrawCallType::MAX, "Invalid draw calls batching descriptor.");
|
|
// @formatter:on
|
|
|
|
bool CanBatchDrawCalls(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
|
|
{
|
|
return d1.Type == d2.Type && CanDrawCallBatch[(int32)d1.Type](d1, d2);
|
|
}
|
|
|
|
void DrawBatch(int32 startIndex, int32 count);
|
|
|
|
bool CachedPSO::Init(GPUShader* shader, bool useDepth)
|
|
{
|
|
if (Inited)
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
UseDepth = useDepth;
|
|
|
|
// Create pipeline states
|
|
GPUPipelineState::Description desc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
|
desc.DepthEnable = desc.DepthWriteEnable = useDepth;
|
|
desc.DepthWriteEnable = false;
|
|
desc.DepthClipEnable = false;
|
|
desc.VS = shader->GetVS("VS");
|
|
desc.PS = shader->GetPS("PS_Image");
|
|
desc.CullMode = CullMode::TwoSided;
|
|
desc.BlendMode = BlendingMode::AlphaBlend;
|
|
PS_Image = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_Image->Init(desc))
|
|
return true;
|
|
//
|
|
desc.BlendMode = BlendingMode::AlphaBlend;
|
|
desc.PS = shader->GetPS("PS_ImagePoint");
|
|
PS_ImagePoint = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_ImagePoint->Init(desc))
|
|
return true;
|
|
//
|
|
desc.BlendMode = BlendingMode::AlphaBlend;
|
|
desc.PS = shader->GetPS("PS_Color");
|
|
PS_Color = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_Color->Init(desc))
|
|
return true;
|
|
//
|
|
desc.BlendMode = BlendingMode::Opaque;
|
|
PS_Color_NoAlpha = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_Color_NoAlpha->Init(desc))
|
|
return true;
|
|
//
|
|
desc.BlendMode = BlendingMode::AlphaBlend;
|
|
desc.PS = shader->GetPS("PS_Font");
|
|
PS_Font = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_Font->Init(desc))
|
|
return true;
|
|
//
|
|
desc.PS = shader->GetPS("PS_LineAA");
|
|
PS_LineAA = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_LineAA->Init(desc))
|
|
return true;
|
|
//
|
|
desc.VS = GPUPipelineState::Description::DefaultFullscreenTriangle.VS;
|
|
desc.PS = shader->GetPS("PS_Blur");
|
|
desc.BlendMode = BlendingMode::Opaque;
|
|
PS_BlurH = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_BlurH->Init(desc))
|
|
return true;
|
|
//
|
|
desc.PS = shader->GetPS("PS_Blur", 1);
|
|
PS_BlurV = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_BlurV->Init(desc))
|
|
return true;
|
|
//
|
|
desc.PS = shader->GetPS("PS_Downscale");
|
|
PS_Downscale = GPUDevice::Instance->CreatePipelineState();
|
|
if (PS_Downscale->Init(desc))
|
|
return true;
|
|
|
|
Inited = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CachedPSO::Dispose()
|
|
{
|
|
if (!Inited)
|
|
return;
|
|
|
|
SAFE_DELETE_GPU_RESOURCE(PS_Image);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_ImagePoint);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_Color);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_Color_NoAlpha);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_Font);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_BlurH);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_BlurV);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_Downscale);
|
|
SAFE_DELETE_GPU_RESOURCE(PS_LineAA);
|
|
|
|
Inited = false;
|
|
}
|
|
|
|
class Render2DService : public EngineService
|
|
{
|
|
public:
|
|
Render2DService()
|
|
: EngineService(TEXT("Render2D"), 10)
|
|
{
|
|
}
|
|
|
|
bool Init() override;
|
|
void Dispose() override;
|
|
};
|
|
|
|
Render2DService Render2DServiceInstance;
|
|
|
|
bool Render2D::IsRendering()
|
|
{
|
|
return Context != nullptr;
|
|
}
|
|
|
|
const Viewport& Render2D::GetViewport()
|
|
{
|
|
return View;
|
|
}
|
|
|
|
#if COMPILE_WITH_DEV_ENV
|
|
|
|
void OnGUIShaderReloading(Asset* obj)
|
|
{
|
|
PsoDepth.Dispose();
|
|
PsoNoDepth.Dispose();
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Render2DService::Init()
|
|
{
|
|
// GUI Shader
|
|
GUIShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/GUI"));
|
|
if (GUIShader == nullptr)
|
|
return true;
|
|
#if COMPILE_WITH_DEV_ENV
|
|
GUIShader.Get()->OnReloading.Bind<OnGUIShaderReloading>();
|
|
#endif
|
|
|
|
DrawCalls.EnsureCapacity(RENDER2D_INITIAL_DRAW_CALL_CAPACITY);
|
|
|
|
return false;
|
|
}
|
|
|
|
void Render2DService::Dispose()
|
|
{
|
|
TintLayersStack.Resize(0);
|
|
ClipLayersStack.Resize(0);
|
|
DrawCalls.Resize(0);
|
|
Lines.Resize(0);
|
|
Lines2.Resize(0);
|
|
|
|
GUIShader = nullptr;
|
|
|
|
PsoDepth.Dispose();
|
|
PsoNoDepth.Dispose();
|
|
|
|
VB.Dispose();
|
|
IB.Dispose();
|
|
}
|
|
|
|
void Render2D::BeginFrame()
|
|
{
|
|
ASSERT(!IsRendering());
|
|
}
|
|
|
|
void Render2D::Begin(GPUContext* context, GPUTexture* output, GPUTexture* depthBuffer)
|
|
{
|
|
ASSERT(output != nullptr);
|
|
Begin(context, output->View(), depthBuffer ? depthBuffer->View() : nullptr, Viewport(output->Size()));
|
|
}
|
|
|
|
void Render2D::Begin(GPUContext* context, GPUTexture* output, GPUTexture* depthBuffer, const Matrix& viewProjection)
|
|
{
|
|
ASSERT(output != nullptr);
|
|
Begin(context, output->View(), depthBuffer ? depthBuffer->View() : nullptr, Viewport(output->Size()), viewProjection);
|
|
}
|
|
|
|
void Render2D::Begin(GPUContext* context, GPUTextureView* output, GPUTextureView* depthBuffer, const Viewport& viewport)
|
|
{
|
|
Matrix view, projection, viewProjection;
|
|
const float halfWidth = viewport.Width * 0.5f;
|
|
const float halfHeight = viewport.Height * 0.5f;
|
|
const float zNear = 0.0f;
|
|
const float zFar = 1.0f;
|
|
Matrix::OrthoOffCenter(-halfWidth, halfWidth, halfHeight, -halfHeight, zNear, zFar, projection);
|
|
Matrix::Translation(-halfWidth, -halfHeight, 0, view);
|
|
Matrix::Multiply(view, projection, viewProjection);
|
|
|
|
Begin(context, output, depthBuffer, viewport, viewProjection);
|
|
|
|
IsScissorsRectEnabled = true;
|
|
}
|
|
|
|
void Render2D::Begin(GPUContext* context, GPUTextureView* output, GPUTextureView* depthBuffer, const Viewport& viewport, const Matrix& viewProjection)
|
|
{
|
|
ASSERT(Context == nullptr && Output == nullptr);
|
|
ASSERT(context != nullptr && output != nullptr);
|
|
|
|
// Setup
|
|
Context = context;
|
|
Output = output;
|
|
DepthBuffer = depthBuffer;
|
|
View = viewport;
|
|
ViewProjection = viewProjection;
|
|
DrawCalls.Clear();
|
|
|
|
// Initialize default transform
|
|
const Matrix3x3 defaultTransform = Matrix3x3::Identity;
|
|
TransformLayersStack.Clear();
|
|
TransformLayersStack.Push(defaultTransform);
|
|
TransformCached = defaultTransform;
|
|
|
|
// Initialize default clip mask
|
|
const Rectangle defaultBounds(viewport.Location, viewport.Size);
|
|
const RotatedRectangle defaultMask(defaultBounds);
|
|
ClipLayersStack.Clear();
|
|
ClipLayersStack.Add({ defaultMask, defaultBounds });
|
|
|
|
// Initialize default tint stack
|
|
TintLayersStack.Clear();
|
|
TintLayersStack.Add({ 1, 1, 1, 1 });
|
|
|
|
// Scissors can be enabled only for 2D orthographic projections
|
|
IsScissorsRectEnabled = false;
|
|
|
|
// Reset geometry buffer
|
|
VB.Clear();
|
|
IB.Clear();
|
|
VBIndex = 0;
|
|
IBIndex = 0;
|
|
}
|
|
|
|
void Render2D::End()
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
ASSERT(Context != nullptr && Output != nullptr);
|
|
ASSERT(GUIShader != nullptr);
|
|
|
|
// Skip if has nothing to draw
|
|
if (DrawCalls.IsEmpty())
|
|
{
|
|
// End
|
|
Context = nullptr;
|
|
Output = nullptr;
|
|
return;
|
|
}
|
|
|
|
PROFILE_GPU_CPU_NAMED("Render2D");
|
|
|
|
// Prepare shader
|
|
GPUShader* shader;
|
|
{
|
|
if (!GUIShader->IsLoaded() && GUIShader->WaitForLoaded())
|
|
{
|
|
// End
|
|
DrawCalls.Clear();
|
|
Context = nullptr;
|
|
Output = nullptr;
|
|
return;
|
|
}
|
|
shader = GUIShader->GetShader();
|
|
}
|
|
|
|
// Flush geometry buffers
|
|
VB.Flush(Context);
|
|
IB.Flush(Context);
|
|
|
|
// Set output
|
|
Context->ResetSR();
|
|
Context->SetRenderTarget(DepthBuffer, Output);
|
|
Context->SetViewportAndScissors(View);
|
|
Context->FlushState();
|
|
|
|
// Prepare constant buffer
|
|
GPUConstantBuffer* constantBuffer = shader->GetCB(0);
|
|
Data data;
|
|
Matrix::Transpose(ViewProjection, data.ViewProjection);
|
|
Context->UpdateCB(constantBuffer, &data);
|
|
Context->BindCB(0, constantBuffer);
|
|
|
|
// Prepare PSO
|
|
if (!PsoDepth.Inited)
|
|
{
|
|
PsoDepth.Init(GUIShader.Get()->GetShader(), true);
|
|
PsoNoDepth.Init(GUIShader.Get()->GetShader(), false);
|
|
}
|
|
CurrentPso = DepthBuffer ? &PsoDepth : &PsoNoDepth;
|
|
|
|
// Flush draw calls
|
|
int32 batchStart = 0, batchSize = 0;
|
|
IsScissorsRectEmpty = false;
|
|
for (int32 i = 0; i < DrawCalls.Count(); i++)
|
|
{
|
|
// Peek draw call
|
|
const auto& drawCall = DrawCalls[i];
|
|
|
|
// Check if cannot add element to the batching
|
|
if (batchSize != 0 && !CanBatchDrawCalls(DrawCalls[batchStart], drawCall))
|
|
{
|
|
// Flush batched elements
|
|
DrawBatch(batchStart, batchSize);
|
|
batchStart += batchSize;
|
|
batchSize = 0;
|
|
}
|
|
|
|
// Add element to batching
|
|
batchSize++;
|
|
}
|
|
|
|
// Flush end of batched elements
|
|
if (batchSize != 0)
|
|
{
|
|
DrawBatch(batchStart, batchSize);
|
|
}
|
|
|
|
// End
|
|
DrawCalls.Clear();
|
|
Context = nullptr;
|
|
Output = nullptr;
|
|
}
|
|
|
|
void Render2D::EndFrame()
|
|
{
|
|
ASSERT(!IsRendering());
|
|
|
|
// Synchronize the texture atlases data
|
|
FontManager::Flush();
|
|
}
|
|
|
|
void Render2D::PushTransform(const Matrix3x3& transform)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
// Combine transformation
|
|
Matrix3x3 finalTransform;
|
|
Matrix3x3::Multiply(transform, TransformCached, finalTransform);
|
|
|
|
// Push it
|
|
TransformLayersStack.Push(finalTransform);
|
|
TransformCached = TransformLayersStack.Peek();
|
|
}
|
|
|
|
void Render2D::PeekTransform(Matrix3x3& transform)
|
|
{
|
|
transform = TransformCached;
|
|
}
|
|
|
|
void Render2D::PopTransform()
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
ASSERT(TransformLayersStack.HasItems());
|
|
TransformLayersStack.Pop();
|
|
TransformCached = TransformLayersStack.Peek();
|
|
}
|
|
|
|
void OnClipScissors()
|
|
{
|
|
if (!IsScissorsRectEnabled)
|
|
return;
|
|
|
|
const auto& mask = ClipLayersStack.Peek();
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::ClipScissors;
|
|
drawCall.AsClipScissors.X = mask.Bounds.GetX();
|
|
drawCall.AsClipScissors.Y = mask.Bounds.GetY();
|
|
drawCall.AsClipScissors.Width = mask.Bounds.GetWidth();
|
|
drawCall.AsClipScissors.Height = mask.Bounds.GetHeight();
|
|
}
|
|
|
|
void Render2D::PushClip(const Rectangle& clipRect)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
RotatedRectangle clipRectTransformed;
|
|
ApplyTransform(clipRect, clipRectTransformed);
|
|
const Rectangle bounds = Rectangle::Shared(clipRectTransformed.ToBoundingRect(), ClipLayersStack.Peek().Bounds);
|
|
ClipLayersStack.Push({ clipRectTransformed, bounds });
|
|
|
|
OnClipScissors();
|
|
}
|
|
|
|
void Render2D::PeekClip(Rectangle& clipRect)
|
|
{
|
|
clipRect = ClipLayersStack.Peek().Bounds;
|
|
}
|
|
|
|
void Render2D::PopClip()
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
ClipLayersStack.Pop();
|
|
|
|
OnClipScissors();
|
|
}
|
|
|
|
void Render2D::PushTint(const Color& tint, bool inherit)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
TintLayersStack.Push(inherit ? tint * TintLayersStack.Peek() : tint);
|
|
}
|
|
|
|
void Render2D::PeekTint(Color& tint)
|
|
{
|
|
tint = TintLayersStack.Peek();
|
|
}
|
|
|
|
void Render2D::PopTint()
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
TintLayersStack.Pop();
|
|
}
|
|
|
|
void CalculateKernelSize(float strength, int32& kernelSize, int32& downSample)
|
|
{
|
|
kernelSize = Math::RoundToInt(strength * 3.0f);
|
|
|
|
if (DownsampleForBlur && kernelSize > 9)
|
|
{
|
|
downSample = kernelSize >= 64 ? 4 : 2;
|
|
kernelSize /= downSample;
|
|
}
|
|
|
|
if (kernelSize % 2 == 0)
|
|
{
|
|
kernelSize++;
|
|
}
|
|
|
|
kernelSize = Math::Clamp(kernelSize, 3, RENDER2D_BLUR_MAX_SAMPLES / 2);
|
|
}
|
|
|
|
static float GetWeight(float dist, float strength)
|
|
{
|
|
float strength2 = strength * strength;
|
|
return (1.0f / Math::Sqrt(2 * PI * strength2)) * Math::Exp(-(dist * dist) / (2 * strength2));
|
|
}
|
|
|
|
static Float2 GetWeightAndOffset(float dist, float sigma)
|
|
{
|
|
float offset1 = dist;
|
|
float weight1 = GetWeight(offset1, sigma);
|
|
|
|
float offset2 = dist + 1;
|
|
float weight2 = GetWeight(offset2, sigma);
|
|
|
|
float totalWeight = weight1 + weight2;
|
|
|
|
float offset = 0;
|
|
if (totalWeight > 0)
|
|
{
|
|
offset = (weight1 * offset1 + weight2 * offset2) / totalWeight;
|
|
}
|
|
|
|
return Float2(totalWeight, offset);
|
|
}
|
|
|
|
static uint32 ComputeBlurWeights(int32 kernelSize, float sigma, Float4* outWeightsAndOffsets)
|
|
{
|
|
const uint32 numSamples = Math::DivideAndRoundUp((uint32)kernelSize, 2u);
|
|
outWeightsAndOffsets[0] = Float4(Float2(GetWeight(0, sigma), 0), GetWeightAndOffset(1, sigma));
|
|
uint32 sampleIndex = 1;
|
|
for (int32 x = 3; x < kernelSize; x += 4)
|
|
{
|
|
outWeightsAndOffsets[sampleIndex] = Float4(GetWeightAndOffset((float)x, sigma), GetWeightAndOffset((float)(x + 2), sigma));
|
|
sampleIndex++;
|
|
}
|
|
return numSamples;
|
|
}
|
|
|
|
void DrawBatch(int32 startIndex, int32 count)
|
|
{
|
|
const Render2DDrawCall& d = DrawCalls[startIndex];
|
|
GPUBuffer* vb = VB.GetBuffer();
|
|
GPUBuffer* ib = IB.GetBuffer();
|
|
uint32 countIb = 0;
|
|
for (int32 i = 0; i < count; i++)
|
|
countIb += DrawCalls[startIndex + i].CountIB;
|
|
|
|
if (d.Type == DrawCallType::ClipScissors)
|
|
{
|
|
Rectangle* scissorsRect = (Rectangle*)&d.AsClipScissors.X;
|
|
Context->SetScissor(*scissorsRect);
|
|
IsScissorsRectEmpty = scissorsRect->Size.IsAnyZero();
|
|
return;
|
|
}
|
|
if (IsScissorsRectEmpty)
|
|
return;
|
|
|
|
switch (d.Type)
|
|
{
|
|
case DrawCallType::FillRect:
|
|
Context->SetState(CurrentPso->PS_Color);
|
|
break;
|
|
case DrawCallType::FillRectNoAlpha:
|
|
Context->SetState(CurrentPso->PS_Color_NoAlpha);
|
|
break;
|
|
case DrawCallType::FillRT:
|
|
Context->BindSR(0, d.AsRT.Ptr);
|
|
Context->SetState(CurrentPso->PS_Image);
|
|
break;
|
|
case DrawCallType::FillTexture:
|
|
Context->BindSR(0, d.AsTexture.Ptr);
|
|
Context->SetState(CurrentPso->PS_Image);
|
|
break;
|
|
case DrawCallType::FillTexturePoint:
|
|
Context->BindSR(0, d.AsTexture.Ptr);
|
|
Context->SetState(CurrentPso->PS_ImagePoint);
|
|
break;
|
|
case DrawCallType::DrawChar:
|
|
Context->BindSR(0, d.AsChar.Tex);
|
|
Context->SetState(CurrentPso->PS_Font);
|
|
break;
|
|
case DrawCallType::DrawCharMaterial:
|
|
{
|
|
// Apply and bind material
|
|
auto material = d.AsChar.Mat;
|
|
MaterialBase::BindParameters bindParams(Context, *(RenderContext*)nullptr);
|
|
Render2D::CustomData customData;
|
|
customData.ViewProjection = ViewProjection;
|
|
customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height);
|
|
bindParams.CustomData = &customData;
|
|
material->Bind(bindParams);
|
|
|
|
// Bind font atlas as a material parameter
|
|
static StringView FontParamName = TEXT("Font");
|
|
auto param = material->Params.Get(FontParamName);
|
|
if (param && param->GetParameterType() == MaterialParameterType::Texture)
|
|
{
|
|
Context->BindSR(param->GetRegister(), d.AsChar.Tex);
|
|
}
|
|
|
|
// Bind index and vertex buffers
|
|
Context->BindIB(ib);
|
|
Context->BindVB(ToSpan(&vb, 1));
|
|
|
|
// Draw
|
|
Context->DrawIndexed(countIb, 0, d.StartIB);
|
|
|
|
// Restore pipeline (material apply overrides it)
|
|
const auto cb = GUIShader->GetShader()->GetCB(0);
|
|
Context->BindCB(0, cb);
|
|
|
|
return;
|
|
}
|
|
case DrawCallType::Custom:
|
|
Context->BindSR(0, d.AsCustom.Tex);
|
|
Context->SetState(d.AsCustom.Pso);
|
|
break;
|
|
case DrawCallType::Material:
|
|
{
|
|
// Bind material
|
|
auto material = (MaterialBase*)d.AsMaterial.Mat;
|
|
MaterialBase::BindParameters bindParams(Context, *(RenderContext*)nullptr);
|
|
Render2D::CustomData customData;
|
|
customData.ViewProjection = ViewProjection;
|
|
customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height);
|
|
bindParams.CustomData = &customData;
|
|
material->Bind(bindParams);
|
|
|
|
// Bind index and vertex buffers
|
|
Context->BindIB(ib);
|
|
Context->BindVB(ToSpan(&vb, 1));
|
|
|
|
// Draw
|
|
Context->DrawIndexed(countIb, 0, d.StartIB);
|
|
|
|
// Restore pipeline (material apply overrides it)
|
|
const auto cb = GUIShader->GetShader()->GetCB(0);
|
|
Context->BindCB(0, cb);
|
|
|
|
return;
|
|
}
|
|
case DrawCallType::Blur:
|
|
{
|
|
PROFILE_GPU("Blur");
|
|
|
|
const Float4 bounds(d.AsBlur.UpperLeftX, d.AsBlur.UpperLeftY, d.AsBlur.BottomRightX, d.AsBlur.BottomRightY);
|
|
float blurStrength = Math::Max(d.AsBlur.Strength, 1.0f);
|
|
const auto& limits = GPUDevice::Instance->Limits;
|
|
int32 renderTargetWidth = Math::Min(Math::RoundToInt(d.AsBlur.Width), limits.MaximumTexture2DSize);
|
|
int32 renderTargetHeight = Math::Min(Math::RoundToInt(d.AsBlur.Height), limits.MaximumTexture2DSize);
|
|
|
|
int32 kernelSize = 0, downSample = 0;
|
|
CalculateKernelSize(blurStrength, kernelSize, downSample);
|
|
if (downSample > 0)
|
|
{
|
|
renderTargetWidth = Math::DivideAndRoundUp(renderTargetWidth, downSample);
|
|
renderTargetHeight = Math::DivideAndRoundUp(renderTargetHeight, downSample);
|
|
blurStrength /= downSample;
|
|
}
|
|
|
|
// Skip if no chance to render anything
|
|
renderTargetWidth = Math::AlignDown(renderTargetWidth, 4);
|
|
renderTargetHeight = Math::AlignDown(renderTargetHeight, 4);
|
|
if (renderTargetWidth <= 0 || renderTargetHeight <= 0)
|
|
return;
|
|
|
|
// Get temporary textures
|
|
auto desc = GPUTextureDescription::New2D(renderTargetWidth, renderTargetHeight, PS_Blur_Format);
|
|
auto blurA = RenderTargetPool::Get(desc);
|
|
auto blurB = RenderTargetPool::Get(desc);
|
|
RENDER_TARGET_POOL_SET_NAME(blurA, "Render2D.BlurA");
|
|
RENDER_TARGET_POOL_SET_NAME(blurB, "Render2D.BlurB");
|
|
|
|
// Prepare blur data
|
|
BlurData data;
|
|
data.Bounds.X = bounds.X;
|
|
data.Bounds.Y = bounds.Y;
|
|
data.Bounds.Z = bounds.Z - bounds.X;
|
|
data.Bounds.W = bounds.W - bounds.Y;
|
|
data.InvBufferSize.X = 1.0f / (float)renderTargetWidth;
|
|
data.InvBufferSize.Y = 1.0f / (float)renderTargetHeight;
|
|
data.SampleCount = ComputeBlurWeights(kernelSize, blurStrength, data.WeightAndOffsets);
|
|
const auto cb = GUIShader->GetShader()->GetCB(1);
|
|
Context->UpdateCB(cb, &data);
|
|
Context->BindCB(1, cb);
|
|
|
|
// Downscale (or not) and extract the background image for the blurring
|
|
Context->ResetRenderTarget();
|
|
Context->SetRenderTarget(blurA->View());
|
|
Context->SetViewportAndScissors((float)renderTargetWidth, (float)renderTargetHeight);
|
|
Context->BindSR(0, Output);
|
|
Context->SetState(CurrentPso->PS_Downscale);
|
|
Context->DrawFullscreenTriangle();
|
|
|
|
// Render the blur (1st pass)
|
|
Context->ResetRenderTarget();
|
|
Context->SetRenderTarget(blurB->View());
|
|
Context->BindSR(0, blurA->View());
|
|
Context->SetState(CurrentPso->PS_BlurH);
|
|
Context->DrawFullscreenTriangle();
|
|
|
|
// Render the blur (2nd pass)
|
|
Context->ResetRenderTarget();
|
|
Context->SetRenderTarget(blurA->View());
|
|
Context->BindSR(0, blurB->View());
|
|
Context->SetState(CurrentPso->PS_BlurV);
|
|
Context->DrawFullscreenTriangle();
|
|
|
|
// Restore output
|
|
Context->ResetRenderTarget();
|
|
Context->SetRenderTarget(DepthBuffer, Output);
|
|
Context->SetViewportAndScissors(View);
|
|
Context->UnBindCB(1);
|
|
|
|
// Link for drawing final blur as a texture
|
|
Context->BindSR(0, blurA->View());
|
|
Context->SetState(CurrentPso->PS_Image);
|
|
|
|
// Cleanup
|
|
RenderTargetPool::Release(blurA);
|
|
RenderTargetPool::Release(blurB);
|
|
|
|
break;
|
|
}
|
|
case DrawCallType::ClipScissors:
|
|
Context->SetScissor(*(Rectangle*)&d.AsClipScissors.X);
|
|
return;
|
|
case DrawCallType::LineAA:
|
|
Context->SetState(CurrentPso->PS_LineAA);
|
|
break;
|
|
#if !BUILD_RELEASE
|
|
default:
|
|
CRASH;
|
|
#endif
|
|
}
|
|
|
|
// Draw
|
|
Context->BindVB(ToSpan(&vb, 1));
|
|
Context->BindIB(ib);
|
|
Context->DrawIndexed(countIb, 0, d.StartIB);
|
|
}
|
|
|
|
void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
// Check if there is no need to do anything
|
|
if (font == nullptr ||
|
|
text.Length() < 0 ||
|
|
(customMaterial && (!customMaterial->IsReady() || !customMaterial->IsGUI())))
|
|
return;
|
|
|
|
// Temporary data
|
|
uint32 fontAtlasIndex = 0;
|
|
FontTextureAtlas* fontAtlas = nullptr;
|
|
Float2 invAtlasSize = Float2::One;
|
|
FontCharacterEntry previous;
|
|
int32 kerning;
|
|
float scale = 1.0f / FontManager::FontScale;
|
|
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
|
|
|
|
// Render all characters
|
|
FontCharacterEntry entry;
|
|
Render2DDrawCall drawCall;
|
|
if (customMaterial)
|
|
{
|
|
drawCall.Type = DrawCallType::DrawCharMaterial;
|
|
drawCall.AsChar.Mat = customMaterial;
|
|
}
|
|
else
|
|
{
|
|
drawCall.Type = DrawCallType::DrawChar;
|
|
drawCall.AsChar.Mat = nullptr;
|
|
}
|
|
Float2 pointer = location;
|
|
for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++)
|
|
{
|
|
// Cache current character
|
|
const Char currentChar = text[currentIndex];
|
|
|
|
// Check if it isn't a newline character
|
|
if (currentChar != '\n')
|
|
{
|
|
// Get character entry
|
|
font->GetCharacter(currentChar, entry, enableFallbackFonts);
|
|
|
|
// Check if need to select/change font atlas (since characters even in the same font may be located in different atlases)
|
|
if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex)
|
|
{
|
|
// Get texture atlas that contains current character
|
|
fontAtlasIndex = entry.TextureIndex;
|
|
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
|
|
if (fontAtlas)
|
|
{
|
|
fontAtlas->EnsureTextureCreated();
|
|
drawCall.AsChar.Tex = fontAtlas->GetTexture();
|
|
invAtlasSize = 1.0f / fontAtlas->GetSize();
|
|
}
|
|
else
|
|
{
|
|
drawCall.AsChar.Tex = nullptr;
|
|
invAtlasSize = 1.0f;
|
|
}
|
|
}
|
|
|
|
// Check if character is a whitespace
|
|
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
|
|
|
|
// Get kerning
|
|
if (!isWhitespace && previous.IsValid)
|
|
{
|
|
kerning = entry.Font->GetKerning(previous.Character, entry.Character);
|
|
}
|
|
else
|
|
{
|
|
kerning = 0;
|
|
}
|
|
pointer.X += kerning * scale;
|
|
previous = entry;
|
|
|
|
// Omit whitespace characters
|
|
if (!isWhitespace)
|
|
{
|
|
// Calculate character size and atlas coordinates
|
|
const float x = pointer.X + entry.OffsetX * scale;
|
|
const float y = pointer.Y + (font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale;
|
|
|
|
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
|
|
|
|
Float2 upperLeftUV = entry.UV * invAtlasSize;
|
|
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
|
|
|
|
// Add draw call
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
DrawCalls.Add(drawCall);
|
|
WriteRect(charRect, color, upperLeftUV, rightBottomUV);
|
|
}
|
|
|
|
// Move
|
|
pointer.X += entry.AdvanceX * scale;
|
|
}
|
|
else
|
|
{
|
|
// Move
|
|
pointer.X = location.X;
|
|
pointer.Y += font->GetHeight() * scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial)
|
|
{
|
|
DrawText(font, textRange.Substring(text), color, location, customMaterial);
|
|
}
|
|
|
|
void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
// Check if there is no need to do anything
|
|
if (font == nullptr ||
|
|
text.IsEmpty() ||
|
|
layout.Scale <= ZeroTolerance ||
|
|
(customMaterial && (!customMaterial->IsReady() || !customMaterial->IsGUI())))
|
|
return;
|
|
|
|
// Temporary data
|
|
uint32 fontAtlasIndex = 0;
|
|
FontTextureAtlas* fontAtlas = nullptr;
|
|
Float2 invAtlasSize = Float2::One;
|
|
FontCharacterEntry previous;
|
|
int32 kerning;
|
|
float scale = layout.Scale / FontManager::FontScale;
|
|
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
|
|
|
|
// Process text to get lines
|
|
Lines.Clear();
|
|
font->ProcessText(text, Lines, layout);
|
|
|
|
// Render all lines
|
|
FontCharacterEntry entry;
|
|
Render2DDrawCall drawCall;
|
|
if (customMaterial)
|
|
{
|
|
drawCall.Type = DrawCallType::DrawCharMaterial;
|
|
drawCall.AsChar.Mat = customMaterial;
|
|
}
|
|
else
|
|
{
|
|
drawCall.Type = DrawCallType::DrawChar;
|
|
drawCall.AsChar.Mat = nullptr;
|
|
}
|
|
for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++)
|
|
{
|
|
const FontLineCache& line = Lines[lineIndex];
|
|
Float2 pointer = line.Location;
|
|
|
|
// Render all characters from the line
|
|
for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++)
|
|
{
|
|
// Cache current character
|
|
const Char currentChar = text[charIndex];
|
|
|
|
// Check if it isn't a newline character
|
|
if (currentChar != '\n')
|
|
{
|
|
// Get character entry
|
|
font->GetCharacter(currentChar, entry, enableFallbackFonts);
|
|
|
|
// Check if need to select/change font atlas (since characters even in the same font may be located in different atlases)
|
|
if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex)
|
|
{
|
|
// Get texture atlas that contains current character
|
|
fontAtlasIndex = entry.TextureIndex;
|
|
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
|
|
if (fontAtlas)
|
|
{
|
|
fontAtlas->EnsureTextureCreated();
|
|
invAtlasSize = 1.0f / fontAtlas->GetSize();
|
|
drawCall.AsChar.Tex = fontAtlas->GetTexture();
|
|
}
|
|
else
|
|
{
|
|
invAtlasSize = 1.0f;
|
|
drawCall.AsChar.Tex = nullptr;
|
|
}
|
|
}
|
|
|
|
// Get kerning
|
|
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
|
|
if (!isWhitespace && previous.IsValid)
|
|
{
|
|
kerning = entry.Font->GetKerning(previous.Character, entry.Character);
|
|
}
|
|
else
|
|
{
|
|
kerning = 0;
|
|
}
|
|
pointer.X += (float)kerning * scale;
|
|
previous = entry;
|
|
|
|
// Omit whitespace characters
|
|
if (!isWhitespace)
|
|
{
|
|
// Calculate character size and atlas coordinates
|
|
const float x = pointer.X + entry.OffsetX * scale;
|
|
const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((font->GetHeight() + font->GetDescender()) * scale);
|
|
|
|
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
|
|
charRect.Offset(layout.Bounds.Location);
|
|
|
|
Float2 upperLeftUV = entry.UV * invAtlasSize;
|
|
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
|
|
|
|
// Add draw call
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
DrawCalls.Add(drawCall);
|
|
WriteRect(charRect, color, upperLeftUV, rightBottomUV);
|
|
}
|
|
|
|
// Move
|
|
pointer.X += entry.AdvanceX * scale;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
|
|
{
|
|
DrawText(font, textRange.Substring(text), color, layout, customMaterial);
|
|
}
|
|
|
|
FORCE_INLINE bool NeedAlphaWithTint(const Color& color)
|
|
{
|
|
return (color.A * TintLayersStack.Peek().A) < 1.0f;
|
|
}
|
|
|
|
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2)
|
|
{
|
|
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f;
|
|
}
|
|
|
|
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2, const Color& color3)
|
|
{
|
|
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f || (color3.A * TintLayersStack.Peek().A) < 1.0f;
|
|
}
|
|
|
|
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2, const Color& color3, const Color& color4)
|
|
{
|
|
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f || (color3.A * TintLayersStack.Peek().A) < 1.0f || (color4.A * TintLayersStack.Peek().A) < 1.0f;
|
|
}
|
|
|
|
void Render2D::FillRectangle(const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::FillRectangle(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = NeedAlphaWithTint(color1, color2, color3, color4) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
WriteRect(rect, color1, color2, color3, color4);
|
|
}
|
|
|
|
void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4, float thickness)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
const auto& mask = ClipLayersStack.Peek().Mask;
|
|
thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f;
|
|
|
|
Float2 points[5];
|
|
ApplyTransform(rect.GetUpperLeft(), points[0]);
|
|
ApplyTransform(rect.GetUpperRight(), points[1]);
|
|
ApplyTransform(rect.GetBottomRight(), points[2]);
|
|
ApplyTransform(rect.GetBottomLeft(), points[3]);
|
|
points[4] = points[0];
|
|
|
|
Color colors[5];
|
|
colors[0] = color1;
|
|
colors[1] = color2;
|
|
colors[2] = color3;
|
|
colors[3] = color4;
|
|
colors[4] = colors[0];
|
|
|
|
Render2DVertex v[4];
|
|
uint32 indices[6];
|
|
Float2 p1t, p2t;
|
|
Color c1t, c2t;
|
|
|
|
p1t = points[0];
|
|
c1t = colors[0];
|
|
|
|
#if RENDER2D_USE_LINE_AA
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::LineAA;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 4 * (6 + 3);
|
|
|
|
// This must be the same as in HLSL code
|
|
const float filterScale = 1.0f;
|
|
const float thicknessHalf = (2.82842712f + thickness) * 0.5f + filterScale;
|
|
|
|
for (int32 i = 1; i < 5; i++)
|
|
{
|
|
p2t = points[i];
|
|
c2t = colors[i];
|
|
|
|
Float2 line = p2t - p1t;
|
|
Float2 up = thicknessHalf * Float2::Normalize(Float2(-line.Y, line.X));
|
|
Float2 right = thicknessHalf * Float2::Normalize(line);
|
|
|
|
// Line
|
|
|
|
v[0] = MakeVertex(p2t + up, Float2::UnitX, c2t, mask, { thickness, (float)Features });
|
|
v[1] = MakeVertex(p1t + up, Float2::UnitX, c1t, mask, { thickness, (float)Features });
|
|
v[2] = MakeVertex(p1t - up, Float2::Zero, c1t, mask, { thickness, (float)Features });
|
|
v[3] = MakeVertex(p2t - up, Float2::Zero, c2t, mask, { thickness, (float)Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 1;
|
|
indices[2] = VBIndex + 2;
|
|
indices[3] = VBIndex + 2;
|
|
indices[4] = VBIndex + 3;
|
|
indices[5] = VBIndex + 0;
|
|
IB.Write(indices, sizeof(uint32) * 6);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
|
|
// Corner cap
|
|
|
|
const float tmp = thickness * 0.69f;
|
|
v[0] = MakeVertex(p2t - up, Float2::Zero, c2t, mask, { tmp, (float)Features });
|
|
v[1] = MakeVertex(p2t + right, Float2::Zero, c2t, mask, { tmp, (float)Features });
|
|
v[2] = MakeVertex(p2t, Float2(0.5f, 0.0f), c2t, mask, { tmp, (float)Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 1;
|
|
indices[1] = VBIndex + 2;
|
|
indices[2] = VBIndex + 0;
|
|
|
|
IB.Write(indices, sizeof(uint32) * 3);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 3;
|
|
|
|
p1t = p2t;
|
|
c1t = c2t;
|
|
}
|
|
#else
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = NeedAlphaWithTint(color1, color2) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 4 * (6 + 3);
|
|
|
|
const float thicknessHalf = thickness * 0.5f;
|
|
|
|
for (int32 i = 1; i < 5; i++)
|
|
{
|
|
p2t = points[i];
|
|
c2t = colors[i];
|
|
|
|
Float2 line = p2t - p1t;
|
|
Float2 up = thicknessHalf * Float2::Normalize(Float2(-line.Y, line.X));
|
|
Float2 right = thicknessHalf * Float2::Normalize(line);
|
|
|
|
// Line
|
|
|
|
v[0] = MakeVertex(p2t + up, Float2::UnitX, c2t, mask, { 0.0f, (float)Features });
|
|
v[1] = MakeVertex(p1t + up, Float2::UnitX, c1t, mask, { 0.0f, (float)Features });
|
|
v[2] = MakeVertex(p1t - up, Float2::Zero, c1t, mask, { 0.0f, (float)Features });
|
|
v[3] = MakeVertex(p2t - up, Float2::Zero, c2t, mask, { 0.0f, (float)Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 1;
|
|
indices[2] = VBIndex + 2;
|
|
indices[3] = VBIndex + 2;
|
|
indices[4] = VBIndex + 3;
|
|
indices[5] = VBIndex + 0;
|
|
IB.Write(indices, sizeof(uint32) * 6);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
|
|
// Corner cap
|
|
|
|
v[0] = MakeVertex(p2t - up, Float2::Zero, c2t, mask, { 0.0f, (float)Features });
|
|
v[1] = MakeVertex(p2t + right, Float2::Zero, c2t, mask, { 0.0f, (float)Features });
|
|
v[2] = MakeVertex(p2t, Float2(0.5f, 0.0f), c2t, mask, { 0.0f, (float)Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 1;
|
|
indices[1] = VBIndex + 2;
|
|
indices[2] = VBIndex + 0;
|
|
|
|
IB.Write(indices, sizeof(uint32) * 3);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 3;
|
|
|
|
p1t = p2t;
|
|
c1t = c2t;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Render2D::DrawTexture(GPUTextureView* rt, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillRT;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsRT.Ptr = rt;
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::DrawTexture(GPUTexture* t, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall drawCall;
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsTexture.Ptr = t;
|
|
DrawCalls.Add(drawCall);
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::DrawTexture(TextureBase* t, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall drawCall;
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr;
|
|
DrawCalls.Add(drawCall);
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
|
|
return;
|
|
|
|
Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
|
|
WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight());
|
|
}
|
|
|
|
void Render2D::DrawTexturePoint(GPUTexture* t, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexturePoint;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsTexture.Ptr = t;
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
|
|
return;
|
|
|
|
Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexturePoint;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
|
|
WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight());
|
|
}
|
|
|
|
void Render2D::Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall drawCall;
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6 * 9;
|
|
drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr;
|
|
DrawCalls.Add(drawCall);
|
|
Write9SlicingRect(rect, color, border, borderUVs);
|
|
}
|
|
|
|
void Render2D::Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall drawCall;
|
|
drawCall.Type = DrawCallType::FillTexturePoint;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6 * 9;
|
|
drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr;
|
|
DrawCalls.Add(drawCall);
|
|
Write9SlicingRect(rect, color, border, borderUVs);
|
|
}
|
|
|
|
void Render2D::Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
|
|
return;
|
|
|
|
Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6 * 9;
|
|
drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
|
|
Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size);
|
|
}
|
|
|
|
void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
|
|
return;
|
|
|
|
Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexturePoint;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6 * 9;
|
|
drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
|
|
Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size);
|
|
}
|
|
|
|
void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState* ps, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (ps == nullptr || !ps->IsValid())
|
|
return;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::Custom;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsCustom.Tex = t;
|
|
drawCall.AsCustom.Pso = ps;
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
#if RENDER2D_USE_LINE_AA
|
|
|
|
void DrawLineCap(const Float2& capOrigin, const Float2& capDirection, const Float2& up, const Color& color, float thickness)
|
|
{
|
|
const auto& mask = ClipLayersStack.Peek().Mask;
|
|
|
|
Render2DVertex v[5];
|
|
v[0] = MakeVertex(capOrigin, Float2(0.5f, 0.0f), color, mask, { thickness, (float)Render2D::Features });
|
|
v[1] = MakeVertex(capOrigin + capDirection + up, Float2::Zero, color, mask, { thickness, (float)Render2D::Features });
|
|
v[2] = MakeVertex(capOrigin + capDirection - up, Float2::Zero, color, mask, { thickness, (float)Render2D::Features });
|
|
v[3] = MakeVertex(capOrigin + up, Float2::Zero, color, mask, { thickness, (float)Render2D::Features });
|
|
v[4] = MakeVertex(capOrigin - up, Float2::Zero, color, mask, { thickness, (float)Render2D::Features });
|
|
VB.Write(v, sizeof(v));
|
|
|
|
uint32 indices[9];
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 3;
|
|
indices[2] = VBIndex + 1;
|
|
indices[3] = VBIndex + 0;
|
|
indices[4] = VBIndex + 1;
|
|
indices[5] = VBIndex + 2;
|
|
indices[6] = VBIndex + 0;
|
|
indices[7] = VBIndex + 2;
|
|
indices[8] = VBIndex + 4;
|
|
IB.Write(indices, sizeof(indices));
|
|
|
|
VBIndex += 5;
|
|
IBIndex += 9;
|
|
}
|
|
|
|
#endif
|
|
|
|
void DrawLines(const Float2* points, int32 pointsCount, const Color& color1, const Color& color2, float thickness)
|
|
{
|
|
ASSERT(points && pointsCount >= 2);
|
|
const auto& mask = ClipLayersStack.Peek().Mask;
|
|
|
|
thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.StartIB = IBIndex;
|
|
|
|
Render2DVertex v[4];
|
|
uint32 indices[6];
|
|
Float2 p1t, p2t;
|
|
|
|
#if RENDER2D_USE_LINE_AA
|
|
// This must be the same as in HLSL code
|
|
const float filterScale = 1.0f;
|
|
const float thicknessHalf = (2.82842712f + thickness) * 0.5f + filterScale;
|
|
|
|
drawCall.Type = DrawCallType::LineAA;
|
|
drawCall.CountIB = 9 + 9;
|
|
|
|
Float2 line;
|
|
Float2 normal;
|
|
Float2 up;
|
|
ApplyTransform(points[0], p1t);
|
|
|
|
// Starting cap
|
|
{
|
|
ApplyTransform(points[1], p2t);
|
|
|
|
line = p2t - p1t;
|
|
normal = Float2::Normalize(Float2(-line.Y, line.X));
|
|
up = normal * thicknessHalf;
|
|
const Float2 capDirection = thicknessHalf * Float2::Normalize(p1t - p2t);
|
|
|
|
DrawLineCap(p1t, capDirection, up, color1, thickness);
|
|
}
|
|
|
|
// Lines
|
|
for (int32 i = 1; i < pointsCount; i++)
|
|
{
|
|
ApplyTransform(points[i], p2t);
|
|
|
|
line = p2t - p1t;
|
|
normal = Float2::Normalize(Float2(-line.Y, line.X));
|
|
up = normal * thicknessHalf;
|
|
|
|
v[0] = MakeVertex(p2t + up, Float2::UnitX, color2, mask, { thickness, (float)Render2D::Features });
|
|
v[1] = MakeVertex(p1t + up, Float2::UnitX, color1, mask, { thickness, (float)Render2D::Features });
|
|
v[2] = MakeVertex(p1t - up, Float2::Zero, color1, mask, { thickness, (float)Render2D::Features });
|
|
v[3] = MakeVertex(p2t - up, Float2::Zero, color2, mask, { thickness, (float)Render2D::Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 1;
|
|
indices[2] = VBIndex + 2;
|
|
indices[3] = VBIndex + 2;
|
|
indices[4] = VBIndex + 3;
|
|
indices[5] = VBIndex + 0;
|
|
IB.Write(indices, sizeof(uint32) * 6);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
drawCall.CountIB += 6;
|
|
|
|
p1t = p2t;
|
|
}
|
|
|
|
// Ending cap
|
|
{
|
|
ApplyTransform(points[0], p1t);
|
|
ApplyTransform(points[1], p2t);
|
|
|
|
const Float2 capDirection = thicknessHalf * Float2::Normalize(p2t - p1t);
|
|
|
|
DrawLineCap(p2t, capDirection, up, color2, thickness);
|
|
}
|
|
#else
|
|
const float thicknessHalf = thickness * 0.5f;
|
|
|
|
drawCall.Type = NeedAlphaWithTint(color1, color2) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.CountIB = 0;
|
|
|
|
ApplyTransform(points[0], p1t);
|
|
for (int32 i = 1; i < pointsCount; i++)
|
|
{
|
|
ApplyTransform(points[i], p2t);
|
|
|
|
const Float2 line = p2t - p1t;
|
|
const Float2 direction = thicknessHalf * Float2::Normalize(p2t - p1t);
|
|
const Float2 normal = Float2::Normalize(Float2(-line.Y, line.X));
|
|
|
|
v[0] = MakeVertex(p2t + thicknessHalf * normal + direction, Float2::Zero, color2, mask, { 0.0f, (float)Render2D::Features });
|
|
v[1] = MakeVertex(p1t + thicknessHalf * normal - direction, Float2::Zero, color1, mask, { 0.0f, (float)Render2D::Features });
|
|
v[2] = MakeVertex(p1t - thicknessHalf * normal - direction, Float2::Zero, color1, mask, { 0.0f, (float)Render2D::Features });
|
|
v[3] = MakeVertex(p2t - thicknessHalf * normal + direction, Float2::Zero, color2, mask, { 0.0f, (float)Render2D::Features });
|
|
VB.Write(v, sizeof(Render2DVertex) * 4);
|
|
|
|
indices[0] = VBIndex + 0;
|
|
indices[1] = VBIndex + 1;
|
|
indices[2] = VBIndex + 2;
|
|
indices[3] = VBIndex + 2;
|
|
indices[4] = VBIndex + 3;
|
|
indices[5] = VBIndex + 0;
|
|
IB.Write(indices, sizeof(uint32) * 6);
|
|
|
|
VBIndex += 4;
|
|
IBIndex += 6;
|
|
drawCall.CountIB += 6;
|
|
|
|
p1t = p2t;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Render2D::DrawLine(const Float2& p1, const Float2& p2, const Color& color1, const Color& color2, float thickness)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Float2 points[2];
|
|
points[0] = p1;
|
|
points[1] = p2;
|
|
|
|
DrawLines(points, 2, color1, color2, thickness);
|
|
}
|
|
|
|
void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
// Find amount of segments to use
|
|
const Float2 d1 = p2 - p1;
|
|
const Float2 d2 = p3 - p2;
|
|
const Float2 d3 = p4 - p3;
|
|
const float len = d1.Length() + d2.Length() + d3.Length();
|
|
const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100);
|
|
const float segmentCountInv = 1.0f / segmentCount;
|
|
|
|
// Draw segmented curve
|
|
Float2 p;
|
|
AnimationUtils::Bezier(p1, p2, p3, p4, 0, p);
|
|
Lines2.Clear();
|
|
Lines2.Add(p);
|
|
for (int32 i = 1; i <= segmentCount; i++)
|
|
{
|
|
const float t = i * segmentCountInv;
|
|
AnimationUtils::Bezier(p1, p2, p3, p4, t, p);
|
|
Lines2.Add(p);
|
|
}
|
|
DrawLines(Lines2.Get(), Lines2.Count(), color, color, thickness);
|
|
}
|
|
|
|
void Render2D::DrawMaterial(MaterialBase* material, const Rectangle& rect, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
if (material == nullptr || !material->IsReady() || !material->IsGUI())
|
|
return;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::Material;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsMaterial.Mat = material;
|
|
drawCall.AsMaterial.Width = rect.GetWidth();
|
|
drawCall.AsMaterial.Height = rect.GetHeight();
|
|
WriteRect(rect, color);
|
|
}
|
|
|
|
void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Float2 p;
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::Blur;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 6;
|
|
drawCall.AsBlur.Strength = blurStrength;
|
|
drawCall.AsBlur.Width = rect.GetWidth();
|
|
drawCall.AsBlur.Height = rect.GetHeight();
|
|
ApplyTransform(rect.GetUpperLeft(), p);
|
|
drawCall.AsBlur.UpperLeftX = p.X;
|
|
drawCall.AsBlur.UpperLeftY = p.Y;
|
|
ApplyTransform(rect.GetBottomRight(), p);
|
|
drawCall.AsBlur.BottomRightX = p.X;
|
|
drawCall.AsBlur.BottomRightY = p.Y;
|
|
WriteRect(rect, Color::White);
|
|
}
|
|
|
|
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
CHECK(vertices.Length() == uvs.Length());
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = vertices.Length();
|
|
drawCall.AsTexture.Ptr = t;
|
|
|
|
for (int32 i = 0; i < vertices.Length(); i += 3)
|
|
WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2]);
|
|
}
|
|
|
|
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Color& color)
|
|
{
|
|
Color colors[3] = { (Color)color, (Color)color, (Color)color };
|
|
Span<Color> spancolor(colors, 3);
|
|
DrawTexturedTriangles(t, vertices, uvs, spancolor);
|
|
}
|
|
|
|
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
CHECK(vertices.Length() == uvs.Length());
|
|
CHECK(vertices.Length() == colors.Length());
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = vertices.Length();
|
|
drawCall.AsTexture.Ptr = t;
|
|
|
|
for (int32 i = 0; i < vertices.Length(); i += 3)
|
|
WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2], colors[i], colors[i + 1], colors[i + 2]);
|
|
}
|
|
|
|
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
CHECK(vertices.Length() == uvs.Length());
|
|
CHECK(vertices.Length() == colors.Length());
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = DrawCallType::FillTexture;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = indices.Length();
|
|
drawCall.AsTexture.Ptr = t;
|
|
|
|
for (int32 i = 0; i < indices.Length();)
|
|
{
|
|
const uint16 i0 = indices.Get()[i++];
|
|
const uint16 i1 = indices.Get()[i++];
|
|
const uint16 i2 = indices.Get()[i++];
|
|
WriteTri(vertices[i0], vertices[i1], vertices[i2], uvs[i0], uvs[i1], uvs[i2], colors[i0], colors[i1], colors[i2]);
|
|
}
|
|
}
|
|
|
|
void Render2D::FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha)
|
|
{
|
|
CHECK(vertices.Length() == colors.Length());
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = useAlpha ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = vertices.Length();
|
|
|
|
for (int32 i = 0; i < vertices.Length(); i += 3)
|
|
WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], colors[i], colors[i + 1], colors[i + 2]);
|
|
}
|
|
|
|
void Render2D::FillTriangle(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color)
|
|
{
|
|
RENDER2D_CHECK_RENDERING_STATE;
|
|
|
|
Render2DDrawCall& drawCall = DrawCalls.AddOne();
|
|
drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
|
|
drawCall.StartIB = IBIndex;
|
|
drawCall.CountIB = 3;
|
|
WriteTri(p0, p1, p2, color, color, color);
|
|
}
|