1681e97a0c
* ATOM-15369 Expose sorting paramers to DynamicDrawContext's draw functions LYN-3352 LyShine: First element in Hierarchy does not properly draw in UI Editor Add function to DynamicDrawContext to support set sort key for followed draw calls. Use different sort key for background draw and top layer draw in UI canvas.
920 lines
37 KiB
C++
920 lines
37 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
#include "LyShine_precompiled.h"
|
|
#include "IFont.h"
|
|
|
|
#include <LyShine/Draw2d.h>
|
|
|
|
#include <AzCore/Math/Matrix3x3.h>
|
|
#include <AzCore/Math/MatrixUtils.h>
|
|
|
|
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
|
#include <Atom/RPI.Public/RPISystemInterface.h>
|
|
#include <Atom/RHI/RHISystemInterface.h>
|
|
#include <Atom/RPI.Public/Shader/Shader.h>
|
|
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
|
#include <Atom/RPI.Public/RPIUtils.h>
|
|
#include <Atom/RPI.Public/ViewportContextBus.h>
|
|
|
|
static const int g_defaultBlendState = GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA;
|
|
static const int g_defaultBaseState = GS_NODEPTHTEST;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// LOCAL STATIC FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Color to u32 => 0xAARRGGBB
|
|
static AZ::u32 PackARGB8888(const AZ::Color& color)
|
|
{
|
|
return (color.GetA8() << 24) | (color.GetR8() << 16) | (color.GetG8() << 8) | color.GetB8();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
CDraw2d::CDraw2d(AZ::RPI::ViewportContextPtr viewportContext)
|
|
: m_deferCalls(false)
|
|
, m_viewportContext(viewportContext)
|
|
{
|
|
// These default options are set here and never change. They are stored so that if a null options
|
|
// structure is passed into the draw functions then this default one can be used instead
|
|
m_defaultImageOptions.blendMode = g_defaultBlendState;
|
|
m_defaultImageOptions.color.Set(1.0f, 1.0f, 1.0f);
|
|
m_defaultImageOptions.pixelRounding = Rounding::Nearest;
|
|
m_defaultImageOptions.baseState = g_defaultBaseState;
|
|
|
|
m_defaultTextOptions.font = (gEnv && gEnv->pCryFont != nullptr) ? gEnv->pCryFont->GetFont("default") : nullptr;
|
|
m_defaultTextOptions.effectIndex = 0;
|
|
m_defaultTextOptions.color.Set(1.0f, 1.0f, 1.0f);
|
|
m_defaultTextOptions.horizontalAlignment = HAlign::Left;
|
|
m_defaultTextOptions.verticalAlignment = VAlign::Top;
|
|
m_defaultTextOptions.dropShadowOffset.Set(0.0f, 0.0f);
|
|
m_defaultTextOptions.dropShadowColor.Set(0.0f, 0.0f, 0.0f, 0.0f);
|
|
m_defaultTextOptions.rotation = 0.0f;
|
|
m_defaultTextOptions.baseState = g_defaultBaseState;
|
|
|
|
AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
CDraw2d::~CDraw2d()
|
|
{
|
|
AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
|
|
{
|
|
// At this point the RPI is ready for use
|
|
|
|
// Load the shader to be used for 2d drawing
|
|
const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
|
|
AZ::Data::Instance<AZ::RPI::Shader> shader = AZ::RPI::LoadShader(shaderFilepath);
|
|
|
|
// Set scene to be associated with the dynamic draw context
|
|
AZ::RPI::ScenePtr scene;
|
|
if (m_viewportContext)
|
|
{
|
|
// Use scene associated with the specified viewport context
|
|
scene = m_viewportContext->GetRenderScene();
|
|
}
|
|
else
|
|
{
|
|
// No viewport context specified, use default scene
|
|
scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
|
|
}
|
|
AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet.");
|
|
|
|
// Create and initialize a DynamicDrawContext for 2d drawing
|
|
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene.get());
|
|
AZ::RPI::ShaderOptionList shaderOptions;
|
|
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true")));
|
|
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("false")));
|
|
m_dynamicDraw->InitShaderWithVariant(shader, &shaderOptions);
|
|
m_dynamicDraw->InitVertexFormat(
|
|
{ {"POSITION", AZ::RHI::Format::R32G32B32_FLOAT},
|
|
{"COLOR", AZ::RHI::Format::B8G8R8A8_UNORM},
|
|
{"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} });
|
|
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::PrimitiveType
|
|
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
|
|
m_dynamicDraw->EndInit();
|
|
|
|
AZ::RHI::TargetBlendState targetBlendState;
|
|
targetBlendState.m_enable = true;
|
|
targetBlendState.m_blendSource = AZ::RHI::BlendFactor::AlphaSource;
|
|
targetBlendState.m_blendDest = AZ::RHI::BlendFactor::AlphaSourceInverse;
|
|
m_dynamicDraw->SetTarget0BlendState(targetBlendState);
|
|
|
|
// Cache draw srg input indices for later use
|
|
static const char textureIndexName[] = "m_texture";
|
|
static const char worldToProjIndexName[] = "m_worldToProj";
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = m_dynamicDraw->NewDrawSrg();
|
|
const AZ::RHI::ShaderResourceGroupLayout* layout = drawSrg->GetAsset()->GetLayout();
|
|
m_shaderData.m_imageInputIndex = layout->FindShaderInputImageIndex(AZ::Name(textureIndexName));
|
|
AZ_Error("Draw2d", m_shaderData.m_imageInputIndex.IsValid(), "Failed to find shader input constant %s.",
|
|
textureIndexName);
|
|
m_shaderData.m_viewProjInputIndex = layout->FindShaderInputConstantIndex(AZ::Name(worldToProjIndexName));
|
|
AZ_Error("Draw2d", m_shaderData.m_viewProjInputIndex.IsValid(), "Failed to find shader input constant %s.",
|
|
worldToProjIndexName);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Draw a textured quad with the top left corner at the given position.
|
|
void CDraw2d::DrawImage(AZ::Data::Instance<AZ::RPI::Image> image, AZ::Vector2 position, AZ::Vector2 size, float opacity,
|
|
float rotation, const AZ::Vector2* pivotPoint, const AZ::Vector2* minMaxTexCoords,
|
|
ImageOptions* imageOptions)
|
|
{
|
|
ImageOptions* actualImageOptions = (imageOptions) ? imageOptions : &m_defaultImageOptions;
|
|
|
|
AZ::Color color = AZ::Color::CreateFromVector3AndFloat(actualImageOptions->color, opacity);
|
|
AZ::u32 packedColor = PackARGB8888(color);
|
|
|
|
int blendMode = actualImageOptions->blendMode;
|
|
|
|
// Depending on the requested pixel rounding setting we may round position to an exact pixel
|
|
AZ::Vector2 pos = Draw2dHelper::RoundXY(position, actualImageOptions->pixelRounding);
|
|
|
|
// define quad (in clockwise order)
|
|
DeferredQuad quad;
|
|
quad.m_points[0].Set(pos.GetX(), pos.GetY());
|
|
quad.m_points[1].Set(pos.GetX() + size.GetX(), pos.GetY());
|
|
quad.m_points[2].Set(pos.GetX() + size.GetX(), pos.GetY() + size.GetY());
|
|
quad.m_points[3].Set(pos.GetX(), pos.GetY() + size.GetY());
|
|
|
|
quad.m_packedColors[0] = packedColor;
|
|
quad.m_packedColors[1] = packedColor;
|
|
quad.m_packedColors[2] = packedColor;
|
|
quad.m_packedColors[3] = packedColor;
|
|
|
|
if (minMaxTexCoords)
|
|
{
|
|
quad.m_texCoords[0] = AZ::Vector2(minMaxTexCoords[0].GetX(), minMaxTexCoords[0].GetY());
|
|
quad.m_texCoords[1] = AZ::Vector2(minMaxTexCoords[1].GetX(), minMaxTexCoords[0].GetY());
|
|
quad.m_texCoords[2] = AZ::Vector2(minMaxTexCoords[1].GetX(), minMaxTexCoords[1].GetY());
|
|
quad.m_texCoords[3] = AZ::Vector2(minMaxTexCoords[0].GetX(), minMaxTexCoords[1].GetY());
|
|
}
|
|
else
|
|
{
|
|
quad.m_texCoords[0].Set(0.0f, 0.0f);
|
|
quad.m_texCoords[1].Set(1.0f, 0.0f);
|
|
quad.m_texCoords[2].Set(1.0f, 1.0f);
|
|
quad.m_texCoords[3].Set(0.0f, 1.0f);
|
|
}
|
|
|
|
quad.m_image = image;
|
|
|
|
// add the blendMode flags to the base state
|
|
quad.m_state = blendMode | actualImageOptions->baseState;
|
|
|
|
// apply rotation if requested
|
|
if (rotation != 0.0f)
|
|
{
|
|
AZ::Vector2 pivot = (pivotPoint) ? *pivotPoint : quad.m_points[0];
|
|
RotatePointsAboutPivot(quad.m_points, 4, pivot, rotation);
|
|
}
|
|
|
|
DrawOrDeferQuad(&quad);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawImageAligned(AZ::Data::Instance<AZ::RPI::Image> image, AZ::Vector2 position, AZ::Vector2 size,
|
|
HAlign horizontalAlignment, VAlign verticalAlignment,
|
|
float opacity, float rotation, const AZ::Vector2* minMaxTexCoords, ImageOptions* imageOptions)
|
|
{
|
|
AZ::Vector2 alignedPosition = Align(position, size, horizontalAlignment, verticalAlignment);
|
|
|
|
DrawImage(image, alignedPosition, size, opacity, rotation, &position, minMaxTexCoords, imageOptions);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawQuad(AZ::Data::Instance<AZ::RPI::Image> image, VertexPosColUV* verts, int blendMode, Rounding pixelRounding, int baseState)
|
|
{
|
|
int actualBlendMode = (blendMode == -1) ? g_defaultBlendState : blendMode;
|
|
int actualBaseState = (baseState == -1) ? g_defaultBaseState : baseState;
|
|
|
|
// define quad
|
|
DeferredQuad quad;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
quad.m_points[i] = Draw2dHelper::RoundXY(verts[i].position, pixelRounding);
|
|
quad.m_texCoords[i] = verts[i].uv;
|
|
quad.m_packedColors[i] = PackARGB8888(verts[i].color);
|
|
}
|
|
quad.m_image = image;
|
|
|
|
// add the blendMode flags to the base state
|
|
quad.m_state = actualBlendMode | actualBaseState;
|
|
|
|
DrawOrDeferQuad(&quad);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawLine(AZ::Vector2 start, AZ::Vector2 end, AZ::Color color, int blendMode, Rounding pixelRounding, int baseState)
|
|
{
|
|
int actualBaseState = (baseState == -1) ? g_defaultBaseState : baseState;
|
|
|
|
auto image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White);
|
|
|
|
int actualBlendMode = (blendMode == -1) ? g_defaultBlendState : blendMode;
|
|
|
|
// define line
|
|
uint32 packedColor = PackARGB8888(color);
|
|
|
|
DeferredLine line;
|
|
line.m_image = image;
|
|
|
|
line.m_points[0] = Draw2dHelper::RoundXY(start, pixelRounding);
|
|
line.m_points[1] = Draw2dHelper::RoundXY(end, pixelRounding);
|
|
|
|
line.m_texCoords[0] = AZ::Vector2(0, 0);
|
|
line.m_texCoords[1] = AZ::Vector2(1, 1);
|
|
|
|
line.m_packedColors[0] = packedColor;
|
|
line.m_packedColors[1] = packedColor;
|
|
|
|
// add the blendMode flags to the base state
|
|
line.m_state = actualBlendMode | actualBaseState;
|
|
|
|
DrawOrDeferLine(&line);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawLineTextured(AZ::Data::Instance<AZ::RPI::Image> image, VertexPosColUV* verts, int blendMode, Rounding pixelRounding, int baseState)
|
|
{
|
|
int actualBaseState = (baseState == -1) ? g_defaultBaseState : baseState;
|
|
|
|
int actualBlendMode = (blendMode == -1) ? g_defaultBlendState : blendMode;
|
|
|
|
// define line
|
|
DeferredLine line;
|
|
line.m_image = image;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
line.m_points[i] = Draw2dHelper::RoundXY(verts[i].position, pixelRounding);
|
|
line.m_texCoords[i] = verts[i].uv;
|
|
line.m_packedColors[i] = PackARGB8888(verts[i].color);
|
|
}
|
|
|
|
// add the blendMode flags to the base state
|
|
line.m_state = actualBlendMode | actualBaseState;
|
|
|
|
DrawOrDeferLine(&line);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// NOTE: The alignment does not take account of the drop shadow. i.e. the non-shadow text is centered
|
|
// and the drop shadow text is offset from that.
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawText(const char* textString, AZ::Vector2 position, float pointSize, float opacity, TextOptions* textOptions)
|
|
{
|
|
TextOptions* actualTextOptions = (textOptions) ? textOptions : &m_defaultTextOptions;
|
|
|
|
// render the drop shadow, if needed
|
|
if ((actualTextOptions->dropShadowColor.GetA() > 0.0f) &&
|
|
(actualTextOptions->dropShadowOffset.GetX() || actualTextOptions->dropShadowOffset.GetY()))
|
|
{
|
|
// calculate the drop shadow pos and render it
|
|
AZ::Vector2 dropShadowPosition(position + actualTextOptions->dropShadowOffset);
|
|
DrawTextInternal(textString, actualTextOptions->font, actualTextOptions->effectIndex,
|
|
dropShadowPosition, pointSize, actualTextOptions->dropShadowColor,
|
|
actualTextOptions->rotation,
|
|
actualTextOptions->horizontalAlignment, actualTextOptions->verticalAlignment,
|
|
actualTextOptions->baseState);
|
|
}
|
|
|
|
// draw the text string
|
|
AZ::Color textColor = AZ::Color::CreateFromVector3AndFloat(actualTextOptions->color, opacity);
|
|
DrawTextInternal(textString, actualTextOptions->font, actualTextOptions->effectIndex,
|
|
position, pointSize, textColor,
|
|
actualTextOptions->rotation,
|
|
actualTextOptions->horizontalAlignment, actualTextOptions->verticalAlignment,
|
|
actualTextOptions->baseState);
|
|
}
|
|
|
|
void CDraw2d::DrawRectOutlineTextured(AZ::Data::Instance<AZ::RPI::Image> image,
|
|
UiTransformInterface::RectPoints points,
|
|
AZ::Vector2 rightVec,
|
|
AZ::Vector2 downVec,
|
|
AZ::Color color)
|
|
{
|
|
// since the rect can be transformed we have to add the offsets by multiplying them
|
|
// by unit vectors parallel with the edges of the rect. However, the rect could be
|
|
// zero width and/or height so we can't use "points" to compute these unit vectors.
|
|
// So we instead get two transformed unit vectors and then normalize them
|
|
rightVec.NormalizeSafe();
|
|
downVec.NormalizeSafe();
|
|
|
|
// calculate the transformed width and height of the rect
|
|
// (in case it is smaller than the texture height)
|
|
AZ::Vector2 widthVec = points.TopRight() - points.TopLeft();
|
|
AZ::Vector2 heightVec = points.BottomLeft() - points.TopLeft();
|
|
float rectWidth = widthVec.GetLength();
|
|
float rectHeight = heightVec.GetLength();
|
|
|
|
// the outline thickness will be based on the texture height
|
|
float textureHeight = image ? aznumeric_cast<float>(image->GetDescriptor().m_size.m_height) : 0.0f;
|
|
if (textureHeight <= 0.0f)
|
|
{
|
|
AZ_Assert(false, "Attempting to draw a textured rect outline with an image of zero height.");
|
|
return; // avoiding possible divide by zero later
|
|
}
|
|
|
|
// the outline is centered on the element rect so half the outline is outside
|
|
// the rect and half is inside the rect
|
|
float outerOffset = -textureHeight * 0.5f;
|
|
float innerOffset = textureHeight * 0.5f;
|
|
float outerV = 0.0f;
|
|
float innerV = 1.0f;
|
|
|
|
// if the rect is small there may not be space for the half of the outline that
|
|
// is inside the rect. If this is the case reduce the innerOffset so the inner
|
|
// points are coincident. Adjust the UVs according to keep a 1-1 texel to pixel ratio.
|
|
float minDimension = min(rectWidth, rectHeight);
|
|
if (innerOffset > minDimension * 0.5f)
|
|
{
|
|
float oldInnerOffset = innerOffset;
|
|
innerOffset = minDimension * 0.5f;
|
|
// note oldInnerOffset can't be zero because of early return if textureHeight is zero
|
|
innerV = 0.5f + 0.5f * innerOffset / oldInnerOffset;
|
|
}
|
|
|
|
DeferredRectOutline rectOutline;
|
|
|
|
// fill out the 8 verts to define the 2 rectangles - outer and inner
|
|
// The vertices are in the order of outer rect then inner rect. e.g.:
|
|
// 0 1
|
|
// 4 5
|
|
// 6 7
|
|
// 2 3
|
|
//
|
|
// four verts of outer rect
|
|
rectOutline.m_verts2d[0] = points.pt[0] + rightVec * outerOffset + downVec * outerOffset;
|
|
rectOutline.m_verts2d[1] = points.pt[1] - rightVec * outerOffset + downVec * outerOffset;
|
|
rectOutline.m_verts2d[2] = points.pt[3] + rightVec * outerOffset - downVec * outerOffset;
|
|
rectOutline.m_verts2d[3] = points.pt[2] - rightVec * outerOffset - downVec * outerOffset;
|
|
// four verts of inner rect
|
|
rectOutline.m_verts2d[4] = points.pt[0] + rightVec * innerOffset + downVec * innerOffset;
|
|
rectOutline.m_verts2d[5] = points.pt[1] - rightVec * innerOffset + downVec * innerOffset;
|
|
rectOutline.m_verts2d[6] = points.pt[3] + rightVec * innerOffset - downVec * innerOffset;
|
|
rectOutline.m_verts2d[7] = points.pt[2] - rightVec * innerOffset - downVec * innerOffset;
|
|
|
|
// and define the UV coordinates for these 8 verts
|
|
rectOutline.m_uvs[0] = AZ::Vector2(0.0f, outerV);
|
|
rectOutline.m_uvs[1] = AZ::Vector2(1.0f, outerV);
|
|
rectOutline.m_uvs[2] = AZ::Vector2(1.0f, outerV);
|
|
rectOutline.m_uvs[3] = AZ::Vector2(0.0f, outerV);
|
|
rectOutline.m_uvs[4] = AZ::Vector2(0.0f, innerV);
|
|
rectOutline.m_uvs[5] = AZ::Vector2(1.0f, innerV);
|
|
rectOutline.m_uvs[6] = AZ::Vector2(1.0f, innerV);
|
|
rectOutline.m_uvs[7] = AZ::Vector2(0.0f, innerV);
|
|
|
|
rectOutline.m_image = image;
|
|
rectOutline.m_color = color;
|
|
|
|
DrawOrDeferRectOutline(&rectOutline);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// TBD should the size include the offset of the drop shadow (if any)?
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 CDraw2d::GetTextSize(const char* textString, float pointSize, TextOptions* textOptions)
|
|
{
|
|
TextOptions* actualTextOptions = (textOptions) ? textOptions : &m_defaultTextOptions;
|
|
|
|
if (!actualTextOptions->font)
|
|
{
|
|
return AZ::Vector2(0.0f, 0.0f);
|
|
}
|
|
|
|
STextDrawContext fontContext;
|
|
fontContext.SetEffect(actualTextOptions->effectIndex);
|
|
fontContext.SetSizeIn800x600(false);
|
|
fontContext.SetSize(vector2f(pointSize, pointSize));
|
|
|
|
Vec2 textSize = actualTextOptions->font->GetTextSize(textString, true, fontContext);
|
|
return AZ::Vector2(textSize.x, textSize.y);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
float CDraw2d::GetViewportWidth() const
|
|
{
|
|
auto windowContext = GetViewportContext()->GetWindowContext();
|
|
const AZ::RHI::Viewport& viewport = windowContext->GetViewport();
|
|
const float viewWidth = viewport.m_maxX - viewport.m_minX;
|
|
return viewWidth;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
float CDraw2d::GetViewportHeight() const
|
|
{
|
|
auto windowContext = GetViewportContext()->GetWindowContext();
|
|
const AZ::RHI::Viewport& viewport = windowContext->GetViewport();
|
|
const float viewHeight = viewport.m_maxY - viewport.m_minY;
|
|
return viewHeight;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const CDraw2d::ImageOptions& CDraw2d::GetDefaultImageOptions() const
|
|
{
|
|
return m_defaultImageOptions;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const CDraw2d::TextOptions& CDraw2d::GetDefaultTextOptions() const
|
|
{
|
|
return m_defaultTextOptions;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::RenderDeferredPrimitives()
|
|
{
|
|
// Draw and delete the deferred primitives
|
|
AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
|
|
for (auto primIter : m_deferredPrimitives)
|
|
{
|
|
primIter->Draw(m_dynamicDraw, m_shaderData, viewportContext);
|
|
delete primIter;
|
|
}
|
|
|
|
// clear the list of deferred primitives
|
|
m_deferredPrimitives.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::SetDeferPrimitives(bool deferPrimitives)
|
|
{
|
|
m_deferCalls = deferPrimitives;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool CDraw2d::GetDeferPrimitives()
|
|
{
|
|
return m_deferCalls;
|
|
}
|
|
|
|
void CDraw2d::SetSortKey(int64_t key)
|
|
{
|
|
m_dynamicDraw->SetSortKey(key);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC STATIC FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 CDraw2d::Align(AZ::Vector2 position, AZ::Vector2 size,
|
|
HAlign horizontalAlignment, VAlign verticalAlignment)
|
|
{
|
|
AZ::Vector2 result;
|
|
switch (horizontalAlignment)
|
|
{
|
|
case HAlign::Left:
|
|
result.SetX(position.GetX());
|
|
break;
|
|
case HAlign::Center:
|
|
result.SetX(position.GetX() - size.GetX() * 0.5f);
|
|
break;
|
|
case HAlign::Right:
|
|
result.SetX(position.GetX() - size.GetX());
|
|
break;
|
|
}
|
|
|
|
switch (verticalAlignment)
|
|
{
|
|
case VAlign::Top:
|
|
result.SetY(position.GetY());
|
|
break;
|
|
case VAlign::Center:
|
|
result.SetY(position.GetY() - size.GetY() * 0.5f);
|
|
break;
|
|
case VAlign::Bottom:
|
|
result.SetY(position.GetY() - size.GetY());
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Data::Instance<AZ::RPI::Image> CDraw2d::LoadTexture(const AZStd::string& pathName)
|
|
{
|
|
AZStd::string sourceRelativePath(pathName);
|
|
AZStd::string cacheRelativePath = sourceRelativePath + ".streamingimage";
|
|
|
|
// The file may not be in the AssetCatalog at this point if it is still processing or doesn't exist on disk.
|
|
// Use GenerateAssetIdTEMP instead of GetAssetIdByPath so that it will return a valid AssetId anyways
|
|
AZ::Data::AssetId streamingImageAssetId;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
|
streamingImageAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GenerateAssetIdTEMP,
|
|
sourceRelativePath.c_str());
|
|
streamingImageAssetId.m_subId = AZ::RPI::StreamingImageAsset::GetImageAssetSubId();
|
|
|
|
auto streamingImageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::RPI::StreamingImageAsset>(streamingImageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
|
|
AZ::Data::Instance<AZ::RPI::Image> image = AZ::RPI::StreamingImage::FindOrCreate(streamingImageAsset);
|
|
if (!image)
|
|
{
|
|
AZ_Error("Draw2d", false, "Failed to find or create an image instance from image asset '%s'", streamingImageAsset.GetHint().c_str());
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::RotatePointsAboutPivot(AZ::Vector2* points, [[maybe_unused]] int numPoints, AZ::Vector2 pivot, float angle) const
|
|
{
|
|
float angleRadians = DEG2RAD(angle);
|
|
AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateRotationZ(angleRadians);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
AZ::Vector2 offset = points[i] - pivot;
|
|
AZ::Vector3 offset3(offset.GetX(), offset.GetY(), 0.0f);
|
|
offset3 = offset3 * rotationMatrix;
|
|
offset.Set(offset3.GetX(), offset3.GetY());
|
|
points[i] = pivot + offset;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawTextInternal(const char* textString, IFFont* font, unsigned int effectIndex,
|
|
AZ::Vector2 position, float pointSize, AZ::Color color, float rotation,
|
|
HAlign horizontalAlignment, VAlign verticalAlignment, int baseState)
|
|
{
|
|
if (!font)
|
|
{
|
|
return;
|
|
}
|
|
|
|
STextDrawContext fontContext;
|
|
fontContext.SetEffect(effectIndex);
|
|
fontContext.SetSizeIn800x600(false);
|
|
fontContext.SetSize(vector2f(pointSize, pointSize));
|
|
fontContext.SetColor(ColorF(color.GetR(), color.GetG(), color.GetB(), color.GetA()));
|
|
fontContext.m_baseState = baseState;
|
|
fontContext.SetOverrideViewProjMatrices(false);
|
|
|
|
// FFont.cpp uses the alpha value of the color to decide whether to use the color, if the alpha value is zero
|
|
// (in a ColorB format) then the color set via SetColor is ignored and it usually ends up drawing with an alpha of 1.
|
|
// This is not what we want so in this case do not draw at all.
|
|
if (!fontContext.IsColorOverridden())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ::Vector2 alignedPosition;
|
|
if (horizontalAlignment == HAlign::Left && verticalAlignment == VAlign::Top)
|
|
{
|
|
alignedPosition = position;
|
|
}
|
|
else
|
|
{
|
|
// we align based on the size of the default font effect, because we do not want the
|
|
// text to move when the font effect is changed
|
|
unsigned int fontEffectIndex = fontContext.m_fxIdx;
|
|
fontContext.SetEffect(0);
|
|
Vec2 textSize = font->GetTextSize(textString, true, fontContext);
|
|
fontContext.SetEffect(fontEffectIndex);
|
|
|
|
alignedPosition = Align(position, AZ::Vector2(textSize.x, textSize.y), horizontalAlignment, verticalAlignment);
|
|
}
|
|
|
|
int flags = 0;
|
|
if (rotation != 0.0f)
|
|
{
|
|
// rotate around the position (if aligned to center will rotate about center etc)
|
|
float rotRad = DEG2RAD(rotation);
|
|
Vec3 pivot(position.GetX(), position.GetY(), 0.0f);
|
|
Matrix34A moveToPivotSpaceMat = Matrix34A::CreateTranslationMat(-pivot);
|
|
Matrix34A rotMat = Matrix34A::CreateRotationZ(rotRad);
|
|
Matrix34A moveFromPivotSpaceMat = Matrix34A::CreateTranslationMat(pivot);
|
|
|
|
Matrix34A transform = moveFromPivotSpaceMat * rotMat * moveToPivotSpaceMat;
|
|
fontContext.SetTransform(transform);
|
|
flags |= eDrawText_UseTransform;
|
|
}
|
|
|
|
// The font system uses these alignment flags to force text to be in the safe zone
|
|
// depending on overscan etc
|
|
if (horizontalAlignment == HAlign::Center)
|
|
{
|
|
flags |= eDrawText_Center;
|
|
}
|
|
else if (horizontalAlignment == HAlign::Right)
|
|
{
|
|
flags |= eDrawText_Right;
|
|
}
|
|
|
|
if (verticalAlignment == VAlign::Center)
|
|
{
|
|
flags |= eDrawText_CenterV;
|
|
}
|
|
else if (verticalAlignment == VAlign::Bottom)
|
|
{
|
|
flags |= eDrawText_Bottom;
|
|
}
|
|
|
|
fontContext.SetFlags(flags);
|
|
|
|
if (m_deferCalls)
|
|
{
|
|
DeferredText* newText = new DeferredText;
|
|
|
|
newText->m_fontContext = fontContext;
|
|
newText->m_font = font;
|
|
newText->m_position = alignedPosition;
|
|
newText->m_string = textString;
|
|
|
|
m_deferredPrimitives.push_back(newText);
|
|
}
|
|
else
|
|
{
|
|
font->DrawString(alignedPosition.GetX(), alignedPosition.GetY(), textString, true, fontContext);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawOrDeferQuad(const DeferredQuad* quad)
|
|
{
|
|
if (m_deferCalls)
|
|
{
|
|
DeferredQuad* newQuad = new DeferredQuad;
|
|
*newQuad = *quad;
|
|
m_deferredPrimitives.push_back(newQuad);
|
|
}
|
|
else
|
|
{
|
|
quad->Draw(m_dynamicDraw, m_shaderData, GetViewportContext());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DrawOrDeferLine(const DeferredLine* line)
|
|
{
|
|
if (m_deferCalls)
|
|
{
|
|
DeferredLine* newLine = new DeferredLine;
|
|
*newLine = *line;
|
|
m_deferredPrimitives.push_back(newLine);
|
|
}
|
|
else
|
|
{
|
|
line->Draw(m_dynamicDraw, m_shaderData, GetViewportContext());
|
|
}
|
|
}
|
|
|
|
void CDraw2d::DrawOrDeferRectOutline(const DeferredRectOutline* rectOutline)
|
|
{
|
|
if (m_deferCalls)
|
|
{
|
|
DeferredRectOutline* newRectOutline = new DeferredRectOutline;
|
|
*newRectOutline = *rectOutline;
|
|
m_deferredPrimitives.push_back(newRectOutline);
|
|
}
|
|
else
|
|
{
|
|
rectOutline->Draw(m_dynamicDraw, m_shaderData, GetViewportContext());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::RPI::ViewportContextPtr CDraw2d::GetViewportContext() const
|
|
{
|
|
if (!m_viewportContext)
|
|
{
|
|
// Return the default viewport context
|
|
auto viewContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
|
|
return viewContextManager->GetDefaultViewportContext();
|
|
}
|
|
|
|
// Return the user specified viewport context
|
|
return m_viewportContext;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// CDraw2d::DeferredQuad
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DeferredQuad::Draw(AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
|
|
const Draw2dShaderData& shaderData,
|
|
AZ::RPI::ViewportContextPtr viewportContext) const
|
|
{
|
|
const int32 NUM_VERTS = 6;
|
|
|
|
const float z = 1.0f; // depth test disabled, if writing Z this will write at far plane
|
|
|
|
SVF_P3F_C4B_T2F vertices[NUM_VERTS];
|
|
const int vertIndex[NUM_VERTS] = {
|
|
0, 1, 3, 3, 1, 2
|
|
};
|
|
|
|
for (int i = 0; i < NUM_VERTS; ++i)
|
|
{
|
|
int j = vertIndex[i];
|
|
vertices[i].xyz = Vec3(m_points[j].GetX(), m_points[j].GetY(), z);
|
|
vertices[i].color.dcolor = m_packedColors[j];
|
|
vertices[i].st = Vec2(m_texCoords[j].GetX(), m_texCoords[j].GetY());
|
|
}
|
|
|
|
// Set up per draw SRG
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
|
|
|
|
// Set texture
|
|
const AZ::RHI::ImageView* imageView = m_image ? m_image->GetImageView() : nullptr;
|
|
if (!imageView)
|
|
{
|
|
// Default to white texture
|
|
auto image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White);
|
|
imageView = image->GetImageView();
|
|
}
|
|
|
|
if (imageView)
|
|
{
|
|
drawSrg->SetImageView(shaderData.m_imageInputIndex, imageView, 0);
|
|
}
|
|
|
|
// Set projection matrix
|
|
auto windowContext = viewportContext->GetWindowContext();
|
|
const AZ::RHI::Viewport& viewport = windowContext->GetViewport();
|
|
const float viewX = viewport.m_minX;
|
|
const float viewY = viewport.m_minY;
|
|
const float viewWidth = viewport.m_maxX - viewport.m_minX;
|
|
const float viewHeight = viewport.m_maxY - viewport.m_minY;
|
|
const float zf = viewport.m_minZ;
|
|
const float zn = viewport.m_maxZ;
|
|
AZ::Matrix4x4 modelViewProjMat;
|
|
AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
|
|
drawSrg->SetConstant(shaderData.m_viewProjInputIndex, modelViewProjMat);
|
|
|
|
drawSrg->Compile();
|
|
|
|
// Add the primitive to the dynamic draw context for drawing
|
|
dynamicDraw->SetPrimitiveType(AZ::RHI::PrimitiveTopology::TriangleList);
|
|
dynamicDraw->DrawLinear(vertices, NUM_VERTS, drawSrg);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// CDraw2d::DeferredLine
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DeferredLine::Draw(AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
|
|
const Draw2dShaderData& shaderData,
|
|
AZ::RPI::ViewportContextPtr viewportContext) const
|
|
{
|
|
const float z = 1.0f; // depth test disabled, if writing Z this will write at far plane
|
|
|
|
const int32 NUM_VERTS = 2;
|
|
|
|
SVF_P3F_C4B_T2F vertices[NUM_VERTS];
|
|
|
|
for (int i = 0; i < NUM_VERTS; ++i)
|
|
{
|
|
vertices[i].xyz = Vec3(m_points[i].GetX(), m_points[i].GetY(), z);
|
|
vertices[i].color.dcolor = m_packedColors[i];
|
|
vertices[i].st = Vec2(m_texCoords[i].GetX(), m_texCoords[i].GetY());
|
|
}
|
|
|
|
// Set up per draw SRG
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
|
|
|
|
// Set texture
|
|
const AZ::RHI::ImageView* imageView = m_image ? m_image->GetImageView() : nullptr;
|
|
if (!imageView)
|
|
{
|
|
// Default to white texture
|
|
auto image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White);
|
|
imageView = image->GetImageView();
|
|
}
|
|
|
|
if (imageView)
|
|
{
|
|
drawSrg->SetImageView(shaderData.m_imageInputIndex, imageView, 0);
|
|
}
|
|
|
|
// Set projection matrix
|
|
auto windowContext = viewportContext->GetWindowContext();
|
|
const AZ::RHI::Viewport& viewport = windowContext->GetViewport();
|
|
const float viewX = viewport.m_minX;
|
|
const float viewY = viewport.m_minY;
|
|
const float viewWidth = viewport.m_maxX - viewport.m_minX;
|
|
const float viewHeight = viewport.m_maxY - viewport.m_minY;
|
|
const float zf = viewport.m_minZ;
|
|
const float zn = viewport.m_maxZ;
|
|
AZ::Matrix4x4 modelViewProjMat;
|
|
AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
|
|
drawSrg->SetConstant(shaderData.m_viewProjInputIndex, modelViewProjMat);
|
|
|
|
drawSrg->Compile();
|
|
|
|
// Add the primitive to the dynamic draw context for drawing
|
|
dynamicDraw->SetPrimitiveType(AZ::RHI::PrimitiveTopology::LineList);
|
|
dynamicDraw->DrawLinear(vertices, NUM_VERTS, drawSrg);
|
|
}
|
|
|
|
void CDraw2d::DeferredRectOutline::Draw(AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
|
|
const Draw2dShaderData& shaderData,
|
|
AZ::RPI::ViewportContextPtr viewportContext) const
|
|
{
|
|
// Create the 8 verts in the right vertex format for the dynamic draw context
|
|
SVF_P3F_C4B_T2F vertices[NUM_VERTS];
|
|
const float z = 1.0f; // depth test disabled, if writing Z this will write at far plane
|
|
uint32 packedColor = (m_color.GetA8() << 24) | (m_color.GetR8() << 16) | (m_color.GetG8() << 8) | m_color.GetB8();
|
|
for (int i = 0; i < NUM_VERTS; ++i)
|
|
{
|
|
vertices[i].xyz = Vec3(m_verts2d[i].GetX(), m_verts2d[i].GetY(), z);
|
|
vertices[i].color.dcolor = packedColor;
|
|
vertices[i].st = Vec2(m_uvs[i].GetX(), m_uvs[i].GetY());
|
|
}
|
|
|
|
// The indices are for four quads (one for each side of the rect).
|
|
// The quads are drawn using a triangle list (simpler than a tri-strip)
|
|
// We draw each quad in the same order that the image component draws quads to
|
|
// maximize chances of things lining up so each quad is drawn as two triangles:
|
|
// top-left, top-right, bottom-left / bottom-left, top-right, bottom-right
|
|
// e.g. for a quad like this:
|
|
//
|
|
// 0 1
|
|
// |/|
|
|
// 2 3
|
|
//
|
|
// The two triangles would be 0,1,2 and 2,1,3
|
|
//
|
|
static constexpr int32 NUM_INDICES = 24;
|
|
uint16 indices[NUM_INDICES] =
|
|
{
|
|
0, 1, 4, 4, 1, 5, // top quad
|
|
6, 7, 2, 2, 7, 3, // bottom quad
|
|
0, 4, 2, 2, 4, 6, // left quad
|
|
5, 1, 7, 1, 7, 3, // right quad
|
|
};
|
|
|
|
// Set up per draw SRG
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
|
|
|
|
// Set texture
|
|
const AZ::RHI::ImageView* imageView = m_image ? m_image->GetImageView() : nullptr;
|
|
if (!imageView)
|
|
{
|
|
// Default to white texture
|
|
auto image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White);
|
|
imageView = image->GetImageView();
|
|
}
|
|
|
|
if (imageView)
|
|
{
|
|
drawSrg->SetImageView(shaderData.m_imageInputIndex, imageView, 0);
|
|
}
|
|
|
|
// Set projection matrix
|
|
auto windowContext = viewportContext->GetWindowContext();
|
|
const AZ::RHI::Viewport& viewport = windowContext->GetViewport();
|
|
const float viewX = viewport.m_minX;
|
|
const float viewY = viewport.m_minY;
|
|
const float viewWidth = viewport.m_maxX - viewport.m_minX;
|
|
const float viewHeight = viewport.m_maxY - viewport.m_minY;
|
|
const float zf = viewport.m_minZ;
|
|
const float zn = viewport.m_maxZ;
|
|
AZ::Matrix4x4 modelViewProjMat;
|
|
AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
|
|
drawSrg->SetConstant(shaderData.m_viewProjInputIndex, modelViewProjMat);
|
|
|
|
drawSrg->Compile();
|
|
|
|
// Add the primitive to the dynamic draw context for drawing
|
|
dynamicDraw->SetPrimitiveType(AZ::RHI::PrimitiveTopology::TriangleList);
|
|
dynamicDraw->DrawIndexed(vertices, NUM_VERTS, indices, NUM_INDICES, AZ::RHI::IndexFormat::Uint16, drawSrg);
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// CDraw2d::DeferredText
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CDraw2d::DeferredText::Draw([[maybe_unused]] AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
|
|
[[maybe_unused]] const Draw2dShaderData& shaderData,
|
|
[[maybe_unused]] AZ::RPI::ViewportContextPtr viewportContext) const
|
|
{
|
|
m_font->DrawString(m_position.GetX(), m_position.GetY(), m_string.c_str(), true, m_fontContext);
|
|
}
|
|
|