2022-05-05 04:33:54 +01:00

1460 lines
42 KiB
C

/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Uefi.h>
#include <IndustryStandard/AppleIcon.h>
#include <IndustryStandard/AppleDiskLabel.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BmpSupportLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/MtrrLib.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcCompressionLib.h>
#include <Library/OcCpuLib.h>
#include <Library/OcGuardLib.h>
#include <Library/OcPngLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include "OpenCanopy.h"
#include "GuiIo.h"
#include "GuiApp.h"
#include "Views/BootPicker.h"
#include "Blending.h"
typedef struct {
UINT32 X;
UINT32 Y;
UINT32 Width;
UINT32 Height;
} GUI_DRAW_REQUEST;
//
// I/O contexts
//
STATIC GUI_OUTPUT_CONTEXT *mOutputContext = NULL;
GLOBAL_REMOVE_IF_UNREFERENCED GUI_POINTER_CONTEXT *mPointerContext = NULL;
GLOBAL_REMOVE_IF_UNREFERENCED GUI_KEY_CONTEXT *mKeyContext = NULL;
//
// Screen buffer information
//
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mScreenBuffer = NULL;
STATIC UINT32 mScreenBufferDelta = 0;
//
// Frame timing information (60 FPS)
//
STATIC UINT64 mDeltaTscTarget = 0;
STATIC UINT64 mStartTsc = 0;
//
// Drawing rectangles information
//
STATIC UINT8 mNumValidDrawReqs = 0;
STATIC GUI_DRAW_REQUEST mDrawRequests[6] = {
{ 0 }
};
STATIC UINT32 mPointerOldDrawBaseX = 0;
STATIC UINT32 mPointerOldDrawBaseY = 0;
STATIC UINT32 mPointerOldDrawWidth = 0;
STATIC UINT32 mPointerOldDrawHeight = 0;
#define PIXEL_TO_UINT32(Pixel) \
((UINT32) SIGNATURE_32 ((Pixel)->Blue, (Pixel)->Green, (Pixel)->Red, (Pixel)->Reserved))
BOOLEAN
GuiClipChildBounds (
IN INT64 ChildOffset,
IN UINT32 ChildLength,
IN OUT UINT32 *ReqOffset,
IN OUT UINT32 *ReqLength
)
{
INT64 OffsetDelta;
UINT32 NewOffset;
UINT32 NewLength;
ASSERT (ReqOffset != NULL);
ASSERT (ReqLength != NULL);
NewOffset = *ReqOffset;
NewLength = *ReqLength;
OffsetDelta = NewOffset - ChildOffset;
if (OffsetDelta >= 0) {
//
// The requested offset starts within or past the child.
//
if (ChildLength <= OffsetDelta) {
//
// The requested offset starts past the child.
//
return FALSE;
}
//
// The requested offset starts within the child.
//
NewOffset = (UINT32)OffsetDelta;
NewLength = MIN (NewLength, (UINT32)(ChildLength - OffsetDelta));
} else {
//
// The requested offset starts before the child.
//
if (NewLength <= -OffsetDelta) {
//
// The requested offset ends before the child.
//
return FALSE;
}
//
// The requested offset ends within the child.
//
NewOffset = 0;
NewLength = MIN ((UINT32)(NewLength + OffsetDelta), ChildLength);
}
ASSERT (ChildOffset + ChildLength > 0);
ASSERT (NewLength > 0);
ASSERT (NewOffset + NewLength <= ChildLength);
*ReqOffset = NewOffset;
*ReqLength = NewLength;
return TRUE;
}
VOID
GuiObjDrawDelegate (
IN OUT GUI_OBJ *This,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN BOOT_PICKER_GUI_CONTEXT *Context,
IN INT64 BaseX,
IN INT64 BaseY,
IN UINT32 OffsetX,
IN UINT32 OffsetY,
IN UINT32 Width,
IN UINT32 Height,
IN UINT8 Opacity
)
{
BOOLEAN Result;
UINTN Index;
GUI_OBJ_CHILD *Child;
UINT32 ChildDrawOffsetX;
UINT32 ChildDrawOffsetY;
UINT32 ChildDrawWidth;
UINT32 ChildDrawHeight;
UINT8 ChildOpacity;
ASSERT (This != NULL);
ASSERT (OffsetX < This->Width);
ASSERT (OffsetY < This->Height);
ASSERT (Width > 0);
ASSERT (Height > 0);
ASSERT (Width <= This->Width);
ASSERT (Height <= This->Height);
ASSERT (DrawContext != NULL);
for (Index = 0; Index < This->NumChildren; ++Index) {
Child = This->Children[Index];
ChildDrawOffsetX = OffsetX;
ChildDrawWidth = Width;
Result = GuiClipChildBounds (
Child->Obj.OffsetX,
Child->Obj.Width,
&ChildDrawOffsetX,
&ChildDrawWidth
);
if (!Result) {
continue;
}
ChildDrawOffsetY = OffsetY;
ChildDrawHeight = Height;
Result = GuiClipChildBounds (
Child->Obj.OffsetY,
Child->Obj.Height,
&ChildDrawOffsetY,
&ChildDrawHeight
);
if (!Result) {
continue;
}
if (Opacity == 0xFF) {
ChildOpacity = Child->Obj.Opacity;
} else if (Child->Obj.Opacity == 0xFF) {
ChildOpacity = Opacity;
} else {
ChildOpacity = RGB_APPLY_OPACITY (Child->Obj.Opacity, Opacity);
}
ASSERT (ChildDrawOffsetX + ChildDrawWidth <= Child->Obj.Width);
ASSERT (ChildDrawOffsetY + ChildDrawHeight <= Child->Obj.Height);
ASSERT (ChildDrawWidth > 0);
ASSERT (ChildDrawHeight > 0);
ASSERT (Child->Obj.Draw != NULL);
Child->Obj.Draw (
&Child->Obj,
DrawContext,
Context,
BaseX + Child->Obj.OffsetX,
BaseY + Child->Obj.OffsetY,
ChildDrawOffsetX,
ChildDrawOffsetY,
ChildDrawWidth,
ChildDrawHeight,
ChildOpacity
);
}
}
GUI_OBJ *
GuiObjDelegatePtrEvent (
IN OUT GUI_OBJ *This,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN BOOT_PICKER_GUI_CONTEXT *Context,
IN INT64 BaseX,
IN INT64 BaseY,
IN CONST GUI_PTR_EVENT *Event
)
{
UINTN Index;
GUI_OBJ *Obj;
GUI_OBJ_CHILD *Child;
ASSERT (This != NULL);
ASSERT (Event->Pos.Pos.X >= BaseX);
ASSERT (Event->Pos.Pos.Y >= BaseY);
ASSERT (This->Width > Event->Pos.Pos.X - BaseX);
ASSERT (This->Height > Event->Pos.Pos.Y - BaseY);
ASSERT (DrawContext != NULL);
//
// Pointer event propagation is backwards due to forwards draw order.
//
for (Index = This->NumChildren; Index > 0; --Index) {
Child = This->Children[Index - 1];
if ( (Event->Pos.Pos.X - BaseX < Child->Obj.OffsetX)
|| (Event->Pos.Pos.X - BaseX >= Child->Obj.OffsetX + Child->Obj.Width)
|| (Event->Pos.Pos.Y - BaseY < Child->Obj.OffsetY)
|| (Event->Pos.Pos.Y - BaseY >= Child->Obj.OffsetY + Child->Obj.Height))
{
continue;
}
ASSERT (Child->Obj.PtrEvent != NULL);
Obj = Child->Obj.PtrEvent (
&Child->Obj,
DrawContext,
Context,
BaseX + Child->Obj.OffsetX,
BaseY + Child->Obj.OffsetY,
Event
);
if (Obj != NULL) {
return Obj;
}
}
return NULL;
}
VOID
GuiDrawToBufferFill (
IN CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Colour,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN UINT32 PosX,
IN UINT32 PosY,
IN UINT32 Width,
IN UINT32 Height
)
{
UINT32 RowIndex;
UINT32 TargetRowOffset;
ASSERT (Colour != NULL);
ASSERT (DrawContext != NULL);
ASSERT (Width > 0);
ASSERT (Height > 0);
//
// Screen cropping happens in GuiRequestDrawCrop().
//
ASSERT (DrawContext->Screen.Width >= PosX);
ASSERT (DrawContext->Screen.Height >= PosY);
ASSERT (PosX + Width <= DrawContext->Screen.Width);
ASSERT (PosY + Height <= DrawContext->Screen.Height);
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
TargetRowOffset = PosY * DrawContext->Screen.Width;
RowIndex < Height;
++RowIndex,
TargetRowOffset += DrawContext->Screen.Width
)
{
//
// Populate the row pixel-by-pixel with Source's (0,0).
//
SetMem32 (
&mScreenBuffer[TargetRowOffset + PosX],
Width * sizeof (UINT32),
PIXEL_TO_UINT32 (Colour)
);
}
//
// TODO: Support opaque fill?
//
#if 0
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
TargetRowOffset = PosY * DrawContext->Screen.Width;
RowIndex < Height;
++RowIndex,
TargetRowOffset += DrawContext->Screen.Width
)
{
//
// Blend the row pixel-by-pixel with Source's (0,0).
//
for (
TargetColumnOffset = PosY;
TargetColumnOffset < PosY + Width;
++TargetColumnOffset
)
{
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
GuiBlendPixel (TargetPixel, &Image->Buffer[0], Opacity);
}
}
#endif
}
VOID
GuiDrawToBuffer (
IN CONST GUI_IMAGE *Image,
IN UINT8 Opacity,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN INT64 BaseX,
IN INT64 BaseY,
IN UINT32 OffsetX,
IN UINT32 OffsetY,
IN UINT32 Width,
IN UINT32 Height
)
{
UINT32 PosX;
UINT32 PosY;
UINT32 RowIndex;
UINT32 SourceRowOffset;
UINT32 TargetRowOffset;
UINT32 SourceColumnOffset;
UINT32 TargetColumnOffset;
CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *SourcePixel;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *TargetPixel;
ASSERT (Image != NULL);
ASSERT (DrawContext != NULL);
ASSERT (BaseX + OffsetX >= 0);
ASSERT (BaseY + OffsetY >= 0);
ASSERT (Width > 0);
ASSERT (Height > 0);
ASSERT (BaseX + OffsetX + Width >= 0);
ASSERT (BaseY + OffsetY + Height >= 0);
ASSERT (BaseX + OffsetX + Width <= MAX_UINT32);
ASSERT (BaseY + OffsetY + Height <= MAX_UINT32);
PosX = (UINT32)(BaseX + OffsetX);
PosY = (UINT32)(BaseY + OffsetY);
//
// Screen cropping happens in GuiRequestDrawCrop().
//
ASSERT (DrawContext->Screen.Width >= PosX);
ASSERT (DrawContext->Screen.Height >= PosY);
ASSERT (PosX + Width <= DrawContext->Screen.Width);
ASSERT (PosY + Height <= DrawContext->Screen.Height);
if (Opacity == 0) {
return;
}
ASSERT (Image->Width > OffsetX);
ASSERT (Image->Height > OffsetY);
//
// Only crop to the image's dimensions when not using fill-drawing.
//
Width = MIN (Width, Image->Width - OffsetX);
Height = MIN (Height, Image->Height - OffsetY);
if ((Width == 0) || (Height == 0)) {
return;
}
ASSERT (Image->Buffer != NULL);
if (Opacity == 0xFF) {
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
SourceRowOffset = OffsetY * Image->Width,
TargetRowOffset = PosY * DrawContext->Screen.Width;
RowIndex < Height;
++RowIndex,
SourceRowOffset += Image->Width,
TargetRowOffset += DrawContext->Screen.Width
)
{
//
// Blend the row pixel-by-pixel.
//
for (
TargetColumnOffset = PosX, SourceColumnOffset = OffsetX;
TargetColumnOffset < PosX + Width;
++TargetColumnOffset, ++SourceColumnOffset
)
{
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
SourcePixel = &Image->Buffer[SourceRowOffset + SourceColumnOffset];
GuiBlendPixelSolid (TargetPixel, SourcePixel);
}
}
} else {
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
SourceRowOffset = OffsetY * Image->Width,
TargetRowOffset = PosY * DrawContext->Screen.Width;
RowIndex < Height;
++RowIndex,
SourceRowOffset += Image->Width,
TargetRowOffset += DrawContext->Screen.Width
)
{
//
// Blend the row pixel-by-pixel.
//
for (
TargetColumnOffset = PosX, SourceColumnOffset = OffsetX;
TargetColumnOffset < PosX + Width;
++TargetColumnOffset, ++SourceColumnOffset
)
{
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
SourcePixel = &Image->Buffer[SourceRowOffset + SourceColumnOffset];
GuiBlendPixelOpaque (TargetPixel, SourcePixel, Opacity);
}
}
}
}
VOID
GuiRequestDraw (
IN UINT32 PosX,
IN UINT32 PosY,
IN UINT32 Width,
IN UINT32 Height
)
{
UINTN Index;
UINT32 ThisArea;
UINT32 ThisMaxXPlus1;
UINT32 ThisMaxYPlus1;
UINT32 ReqMaxXPlus1;
UINT32 ReqMaxYPlus1;
UINT32 ReqArea;
UINT32 CombX;
UINT32 CombY;
UINT32 CombWidth;
UINT32 CombHeight;
UINT32 CombArea;
ThisMaxXPlus1 = PosX + Width;
ThisMaxYPlus1 = PosY + Height;
ThisArea = Width * Height;
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
//
// Calculate several dimensions to determine whether to merge the two
// draw requests for improved flushing performance.
//
ReqMaxXPlus1 = mDrawRequests[Index].X + mDrawRequests[Index].Width;
ReqMaxYPlus1 = mDrawRequests[Index].Y + mDrawRequests[Index].Height;
ReqArea = mDrawRequests[Index].Width * mDrawRequests[Index].Height;
if (mDrawRequests[Index].X < PosX) {
CombX = mDrawRequests[Index].X;
} else {
CombX = PosX;
}
if (ReqMaxXPlus1 > ThisMaxXPlus1) {
CombWidth = ReqMaxXPlus1;
} else {
CombWidth = ThisMaxXPlus1;
}
CombWidth -= CombX;
if (mDrawRequests[Index].Y < PosY) {
CombY = mDrawRequests[Index].Y;
} else {
CombY = PosY;
}
if (ReqMaxYPlus1 > ThisMaxYPlus1) {
CombHeight = ReqMaxYPlus1;
} else {
CombHeight = ThisMaxYPlus1;
}
CombHeight -= CombY;
CombArea = CombWidth * CombHeight;
//
// Two requests are merged when the overarching rectangle is not bigger than
// the two separate rectangles (not accounting for the overlap, as it would
// be drawn twice).
//
// TODO: Profile a good constant factor?
//
if (ThisArea + ReqArea >= CombArea) {
mDrawRequests[Index].X = CombX;
mDrawRequests[Index].Y = CombY;
mDrawRequests[Index].Width = CombWidth;
mDrawRequests[Index].Height = CombHeight;
return;
}
}
if (mNumValidDrawReqs >= ARRAY_SIZE (mDrawRequests)) {
ASSERT (FALSE);
return;
}
mDrawRequests[mNumValidDrawReqs].X = PosX;
mDrawRequests[mNumValidDrawReqs].Y = PosY;
mDrawRequests[mNumValidDrawReqs].Width = Width;
mDrawRequests[mNumValidDrawReqs].Height = Height;
++mNumValidDrawReqs;
}
VOID
GuiRequestDrawCrop (
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN INT64 X,
IN INT64 Y,
IN UINT32 Width,
IN UINT32 Height
)
{
UINT32 PosX;
UINT32 PosY;
INT64 EffWidth;
INT64 EffHeight;
ASSERT (DrawContext != NULL);
EffWidth = Width;
EffHeight = Height;
//
// Only draw the onscreen parts.
//
if (X >= 0) {
PosX = (UINT32)X;
} else {
EffWidth += X;
PosX = 0;
}
if (Y >= 0) {
PosY = (UINT32)Y;
} else {
EffHeight += Y;
PosY = 0;
}
EffWidth = MIN (EffWidth, (INT64)DrawContext->Screen.Width - PosX);
EffHeight = MIN (EffHeight, (INT64)DrawContext->Screen.Height - PosY);
if ((EffWidth <= 0) || (EffHeight <= 0)) {
return;
}
GuiRequestDraw (PosX, PosY, (UINT32)EffWidth, (UINT32)EffHeight);
}
VOID
GuiOverlayPointer (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
CONST GUI_IMAGE *CursorImage;
UINT32 MaxWidth;
UINT32 MaxHeight;
GUI_PTR_POSITION PointerPos;
INT64 BaseX;
INT64 BaseY;
UINT32 ImageOffsetX;
UINT32 ImageOffsetY;
UINT32 DrawBaseX;
UINT32 DrawBaseY;
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->GetCursorImage != NULL);
CursorImage = DrawContext->GetCursorImage (DrawContext->GuiContext);
ASSERT (CursorImage != NULL);
//
// Poll the current cursor position late to reduce input lag.
//
GuiPointerGetPosition (mPointerContext, &PointerPos);
ASSERT (PointerPos.Pos.X < DrawContext->Screen.Width);
ASSERT (PointerPos.Pos.Y < DrawContext->Screen.Height);
//
// Unconditionally draw the cursor to increase frametime consistency and
// prevent situational hiding.
//
// The original area of the cursor is restored at the beginning of the main
// drawing loop.
//
//
// Draw the new cursor at the new position.
//
BaseX = (INT64)PointerPos.Pos.X - BOOT_CURSOR_OFFSET * DrawContext->Scale;
if (BaseX < 0) {
ImageOffsetX = (UINT32)-BaseX;
DrawBaseX = 0;
} else {
ImageOffsetX = 0;
DrawBaseX = (UINT32)BaseX;
}
//
// MaxWidth/Height are for subsequent GuiDrawToBuffer, but also for
// saved PointerOldDraw size below; so we replicate out here clipping
// which is done inside GuiDrawToBuffer, in order to only redraw the
// minimum required.
// BaseX/Y may be negative but will then add, and will not overflow
// when within valid bounds.
//
MaxWidth = MIN (CursorImage->Width - ImageOffsetX, (UINT32)(DrawContext->Screen.Width - BaseX));
BaseY = (INT64)PointerPos.Pos.Y - BOOT_CURSOR_OFFSET * DrawContext->Scale;
if (BaseY < 0) {
ImageOffsetY = (UINT32)-BaseY;
DrawBaseY = 0;
} else {
ImageOffsetY = 0;
DrawBaseY = (UINT32)BaseY;
}
MaxHeight = MIN (CursorImage->Height - ImageOffsetY, (UINT32)(DrawContext->Screen.Height - BaseY));
GuiDrawToBuffer (
CursorImage,
DrawContext->CursorOpacity,
DrawContext,
BaseX,
BaseY,
ImageOffsetX,
ImageOffsetY,
MaxWidth,
MaxHeight
);
//
// Queue a draw request for the newly drawn cursor.
//
GuiRequestDraw (
DrawBaseX,
DrawBaseY,
MaxWidth,
MaxHeight
);
mPointerOldDrawBaseX = DrawBaseX;
mPointerOldDrawBaseY = DrawBaseY;
mPointerOldDrawWidth = MaxWidth;
mPointerOldDrawHeight = MaxHeight;
}
/**
Stalls the CPU for at least the given number of ticks.
Stalls the CPU for at least the given number of ticks. It's invoked by
MicroSecondDelay() and NanoSecondDelay().
@param Delay A period of time to delay in ticks.
**/
STATIC
UINT64
InternalCpuDelayTsc (
IN UINT64 Delay
)
{
UINT64 Ticks;
UINT64 Tsc;
//
// The target timer count is calculated here
//
Ticks = AsmReadTsc () + Delay;
//
// Wait until time out
// Timer wrap-arounds are NOT handled correctly by this function.
// Thus, this function must be called within 10 years of reset since
// Intel guarantees a minimum of 10 years before the TSC wraps.
//
while ((Tsc = AsmReadTsc ()) < Ticks) {
CpuPause ();
}
return Tsc;
}
VOID
GuiFlushScreen (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
UINTN Index;
UINTN ReverseIndex;
UINT64 EndTsc;
UINT64 DeltaTsc;
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->Screen.OffsetX == 0);
ASSERT (DrawContext->Screen.OffsetY == 0);
ASSERT (DrawContext->Screen.Draw != NULL);
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
DrawContext->Screen.Draw (
&DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
0,
0,
mDrawRequests[Index].X,
mDrawRequests[Index].Y,
mDrawRequests[Index].Width,
mDrawRequests[Index].Height,
DrawContext->Screen.Opacity
);
}
EndTsc = AsmReadTsc ();
DeltaTsc = EndTsc - mStartTsc;
if (DeltaTsc < mDeltaTscTarget) {
EndTsc = InternalCpuDelayTsc (mDeltaTscTarget - DeltaTsc);
}
if (mPointerContext != NULL) {
GuiOverlayPointer (DrawContext);
}
//
// FIXME: Reversing the blit order here has several benefits, due to the implicit
// old-before-new ordering which has been used when making the draw requests. The whole
// screen is updated in the memory buffer (by redrawing just the changing parts), then
// these parts are transferred one by one to the video memory.
// Due to lack of vsync in UEFI, any point through this transfer can be visible on screen.
// Reversing the blit order means:
// - Combined old+new mouse pointer area can no longer show part of moving text one frame
// ahead of the rest (see REF); moving text may instead 'tear' mouse, but this is
// much less visible
// - If mouse pointer is moving fast (old+new area is not merged), new pointer
// is always added before old pointer is removed, avoiding mouse disappearing
// - (A similar logic to the preceding should apply e.g. to the moving OS selector too)
// So reversing here gives a 'free' small but visible improvement in the display, given the
// current draw order; but a better full fix would be to specify explicit ordering
// requirements with each draw request, which would allow the best blit order to get this
// same improvement to be calculated independently of the order in which requests are added.
// REF: https://github.com/acidanthera/bugtracker/issues/1852
//
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
ReverseIndex = mNumValidDrawReqs - Index - 1;
GuiOutputBlt (
mOutputContext,
mScreenBuffer,
EfiBltBufferToVideo,
mDrawRequests[ReverseIndex].X,
mDrawRequests[ReverseIndex].Y,
mDrawRequests[ReverseIndex].X,
mDrawRequests[ReverseIndex].Y,
mDrawRequests[ReverseIndex].Width,
mDrawRequests[ReverseIndex].Height,
mScreenBufferDelta
);
}
mNumValidDrawReqs = 0;
//
// Explicitly include BLT time in the timing calculation.
// FIXME: GOP takes inconsistently long depending on dimensions.
//
mStartTsc = EndTsc;
}
VOID
GuiRedrawAndFlushScreen (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
ASSERT (DrawContext != NULL);
mStartTsc = AsmReadTsc ();
GuiRequestDraw (0, 0, DrawContext->Screen.Width, DrawContext->Screen.Height);
GuiFlushScreen (DrawContext);
}
EFI_STATUS
GuiLibConstruct (
IN BOOT_PICKER_GUI_CONTEXT *GuiContext,
IN INT32 CursorOffsetX,
IN INT32 CursorOffsetY
)
{
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
INT64 CursorX;
INT64 CursorY;
mOutputContext = GuiOutputConstruct (GuiContext->Scale);
if (mOutputContext == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise output\n"));
return EFI_UNSUPPORTED;
}
OutputInfo = GuiOutputGetInfo (mOutputContext);
ASSERT (OutputInfo != NULL);
if ((GuiContext->PickerContext->PickerAttributes & OC_ATTR_USE_POINTER_CONTROL) != 0) {
CursorX = (INT64)CursorOffsetX + OutputInfo->HorizontalResolution / 2;
if (CursorX < 0) {
CursorX = 0;
} else if (CursorX > OutputInfo->HorizontalResolution - 1) {
CursorX = OutputInfo->HorizontalResolution - 1;
}
CursorY = (INT64)CursorOffsetY + OutputInfo->VerticalResolution / 2;
if (CursorY < 0) {
CursorY = 0;
} else if (CursorY > OutputInfo->VerticalResolution - 1) {
CursorY = OutputInfo->VerticalResolution - 1;
}
mPointerContext = GuiPointerConstruct (
(UINT32)CursorX,
(UINT32)CursorY,
OutputInfo->HorizontalResolution,
OutputInfo->VerticalResolution,
GuiContext->Scale
);
if (mPointerContext == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise pointer\n"));
}
}
mKeyContext = GuiKeyConstruct (GuiContext->PickerContext);
if (mKeyContext == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise key input\n"));
}
if ((mPointerContext == NULL) && (mKeyContext == NULL)) {
GuiLibDestruct ();
return EFI_UNSUPPORTED;
}
mScreenBufferDelta = OutputInfo->HorizontalResolution * sizeof (*mScreenBuffer);
mScreenBuffer = AllocatePool (OutputInfo->VerticalResolution * mScreenBufferDelta);
if (mScreenBuffer == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: GUI alloc failure\n"));
GuiLibDestruct ();
return EFI_OUT_OF_RESOURCES;
}
MtrrSetMemoryAttribute (
(EFI_PHYSICAL_ADDRESS)(UINTN)mScreenBuffer,
mScreenBufferDelta * OutputInfo->VerticalResolution,
CacheWriteBack
);
mDeltaTscTarget = DivU64x32 (OcGetTSCFrequency (), 60);
return EFI_SUCCESS;
}
VOID
GuiLibDestruct (
VOID
)
{
if (mOutputContext != NULL) {
GuiOutputDestruct (mOutputContext);
mOutputContext = NULL;
}
if (mPointerContext != NULL) {
GuiPointerDestruct (mPointerContext);
mPointerContext = NULL;
}
if (mKeyContext != NULL) {
GuiKeyDestruct (mKeyContext);
mKeyContext = NULL;
}
}
VOID
GuiViewInitialize (
OUT GUI_DRAWING_CONTEXT *DrawContext,
IN BOOT_PICKER_GUI_CONTEXT *GuiContext,
IN CONST GUI_VIEW_CONTEXT *ViewContext
)
{
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
ASSERT (DrawContext != NULL);
OutputInfo = GuiOutputGetInfo (mOutputContext);
ASSERT (OutputInfo != NULL);
DrawContext->CursorOpacity = 0xFF;
DrawContext->Scale = GuiContext->Scale;
DrawContext->Screen.OffsetX = 0;
DrawContext->Screen.OffsetY = 0;
DrawContext->Screen.Width = OutputInfo->HorizontalResolution;
DrawContext->Screen.Height = OutputInfo->VerticalResolution;
DrawContext->Screen.Opacity = 0xFF;
DrawContext->Screen.Draw = ViewContext->Draw;
DrawContext->Screen.KeyEvent = ViewContext->KeyEvent;
DrawContext->Screen.PtrEvent = ViewContext->PtrEvent;
DrawContext->Screen.NumChildren = ViewContext->NumChildren;
DrawContext->Screen.Children = ViewContext->Children;
DrawContext->GetCursorImage = ViewContext->GetCursorImage;
DrawContext->ExitLoop = ViewContext->ExitLoop;
DrawContext->GuiContext = GuiContext;
InitializeListHead (&DrawContext->Animations);
}
VOID
GuiViewDeinitialize (
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
OUT BOOT_PICKER_GUI_CONTEXT *GuiContext
)
{
GUI_PTR_POSITION CursorPosition;
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
if (mPointerContext != NULL) {
OutputInfo = GuiOutputGetInfo (mOutputContext);
GuiPointerGetPosition (mPointerContext, &CursorPosition);
GuiContext->CursorOffsetX = (INT32)((INT64)CursorPosition.Pos.X - OutputInfo->HorizontalResolution / 2);
GuiContext->CursorOffsetY = (INT32)((INT64)CursorPosition.Pos.Y - OutputInfo->VerticalResolution / 2);
}
ZeroMem (DrawContext, sizeof (*DrawContext));
}
VOID
GuiGetBaseCoords (
IN GUI_OBJ *This,
IN GUI_DRAWING_CONTEXT *DrawContext,
OUT INT64 *BaseX,
OUT INT64 *BaseY
)
{
GUI_OBJ *Obj;
GUI_OBJ_CHILD *ChildObj;
INT64 X;
INT64 Y;
UINT32 Index;
ASSERT (This != NULL);
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->Screen.OffsetX == 0);
ASSERT (DrawContext->Screen.OffsetY == 0);
ASSERT (BaseX != NULL);
ASSERT (BaseY != NULL);
X = 0;
Y = 0;
Obj = This;
do {
X += Obj->OffsetX;
Y += Obj->OffsetY;
ChildObj = BASE_CR (Obj, GUI_OBJ_CHILD, Obj);
Obj = ChildObj->Parent;
DEBUG_CODE_BEGIN ();
if (Obj != NULL) {
for (Index = 0; Index < Obj->NumChildren; ++Index) {
if (Obj->Children[Index] == ChildObj) {
break;
}
}
ASSERT (Index != Obj->NumChildren);
}
DEBUG_CODE_END ();
} while (Obj != NULL);
DEBUG_CODE_BEGIN ();
for (Index = 0; Index < DrawContext->Screen.NumChildren; ++Index) {
if (DrawContext->Screen.Children[Index] == ChildObj) {
break;
}
}
ASSERT (Index != DrawContext->Screen.NumChildren);
DEBUG_CODE_END ();
*BaseX = X;
*BaseY = Y;
}
VOID
GuiDrawLoop (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
BOOLEAN Result;
GUI_KEY_EVENT KeyEvent;
GUI_PTR_EVENT PointerEvent;
GUI_OBJ *TempObject;
GUI_OBJ *HoldObject;
INT64 HoldObjBaseX;
INT64 HoldObjBaseY;
CONST LIST_ENTRY *AnimEntry;
GUI_ANIMATION *Animation;
UINT64 LoopStartTsc;
UINT64 LastTsc;
UINT64 NewLastTsc;
ASSERT (DrawContext != NULL);
mNumValidDrawReqs = 0;
DrawContext->FrameTime = 0;
HoldObject = NULL;
//
// Clear previous inputs.
//
if (mPointerContext != NULL) {
GuiPointerReset (mPointerContext);
}
GuiKeyReset (mKeyContext);
//
// Pointer state will be implicitly initialised on the first call in the loop.
//
//
// Main drawing loop, time and derieve sub-frequencies as required.
//
LastTsc = LoopStartTsc = mStartTsc = AsmReadTsc ();
do {
if (mPointerContext != NULL) {
//
// Restore the rectangle previously covered by the cursor.
// The new cursor is drawn right before flushing the screen.
//
GuiRequestDraw (
mPointerOldDrawBaseX,
mPointerOldDrawBaseY,
mPointerOldDrawWidth,
mPointerOldDrawHeight
);
//
// Process pointer events.
//
Result = GuiPointerGetEvent (mPointerContext, &PointerEvent);
if (Result) {
if (PointerEvent.Type == GuiPointerPrimaryUp) {
//
// 'Button down' must have caught and set an interaction object, but
// it may be NULL for objects that solely delegate pointer events.
//
if (HoldObject != NULL) {
GuiGetBaseCoords (
HoldObject,
DrawContext,
&HoldObjBaseX,
&HoldObjBaseY
);
HoldObject->PtrEvent (
HoldObject,
DrawContext,
DrawContext->GuiContext,
HoldObjBaseX,
HoldObjBaseY,
&PointerEvent
);
HoldObject = NULL;
}
} else {
//
// HoldObject == NULL cannot be tested here as double-click may arrive
// before button up.
//
ASSERT (PointerEvent.Type != GuiPointerPrimaryUp);
TempObject = DrawContext->Screen.PtrEvent (
&DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
0,
0,
&PointerEvent
);
if (PointerEvent.Type == GuiPointerPrimaryDown) {
HoldObject = TempObject;
}
}
//
// If detected pointer press then disable menu timeout
//
if (DrawContext->TimeOutSeconds > 0) {
//
// Voice only unrelated key press.
//
if ( !DrawContext->GuiContext->ReadyToBoot
&& DrawContext->GuiContext->PickerContext->PickerAudioAssist)
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_ABORT_TIMEOUT,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
}
DrawContext->TimeOutSeconds = 0;
}
} else if (HoldObject != NULL) {
//
// If there are no events to process, update the cursor position with
// the interaction object for visual effects.
//
PointerEvent.Type = GuiPointerPrimaryDown;
GuiPointerGetPosition (mPointerContext, &PointerEvent.Pos);
GuiGetBaseCoords (
HoldObject,
DrawContext,
&HoldObjBaseX,
&HoldObjBaseY
);
HoldObject->PtrEvent (
HoldObject,
DrawContext,
DrawContext->GuiContext,
HoldObjBaseX,
HoldObjBaseY,
&PointerEvent
);
}
}
if (mKeyContext != NULL) {
//
// Process key events. Only allow one key at a time for now.
//
Result = GuiKeyGetEvent (mKeyContext, &KeyEvent);
if (Result) {
DrawContext->Screen.KeyEvent (
&DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
&KeyEvent
);
//
// If detected key press then disable menu timeout
//
if (DrawContext->TimeOutSeconds > 0) {
//
// Voice only unrelated key press.
//
if ( !DrawContext->GuiContext->ReadyToBoot
&& DrawContext->GuiContext->PickerContext->PickerAudioAssist)
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_ABORT_TIMEOUT,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
}
DrawContext->TimeOutSeconds = 0;
}
}
}
//
// Process queued animations.
//
AnimEntry = GetFirstNode (&DrawContext->Animations);
while (!IsNull (&DrawContext->Animations, AnimEntry)) {
Animation = BASE_CR (AnimEntry, GUI_ANIMATION, Link);
Result = Animation->Animate (Animation->Context, DrawContext, DrawContext->FrameTime);
AnimEntry = GetNextNode (&DrawContext->Animations, AnimEntry);
if (Result) {
RemoveEntryList (&Animation->Link);
InitializeListHead (&Animation->Link);
}
}
++DrawContext->FrameTime;
//
// Flush the changes performed in this refresh iteration.
//
GuiFlushScreen (DrawContext);
NewLastTsc = AsmReadTsc ();
if ( (DrawContext->GuiContext->AudioPlaybackTimeout >= 0)
&& DrawContext->GuiContext->PickerContext->PickerAudioAssist)
{
DrawContext->GuiContext->AudioPlaybackTimeout -= (INT32)(DivU64x32 (
GetTimeInNanoSecond (NewLastTsc - LastTsc),
1000000
));
if (DrawContext->GuiContext->AudioPlaybackTimeout <= 0) {
switch (DrawContext->GuiContext->VoAction) {
case CanopyVoSelectedEntry:
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_SELECTED,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
DrawContext->GuiContext->PickerContext->PlayAudioEntry (
DrawContext->GuiContext->PickerContext,
DrawContext->GuiContext->BootEntry
);
break;
}
case CanopyVoFocusPassword:
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_ENTER_PASSWORD,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
TRUE
);
break;
}
case CanopyVoFocusShutDown:
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_SELECTED,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_SHUT_DOWN,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
TRUE
);
break;
}
case CanopyVoFocusRestart:
{
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_SELECTED,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_RESTART,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
TRUE
);
break;
}
default:
{
ASSERT (FALSE);
break;
}
}
//
// Avoid playing twice if we reach precisely 0.
//
DrawContext->GuiContext->AudioPlaybackTimeout = -1;
}
}
//
// Exit early if reach timer timeout and timer isn't disabled due to key event
//
if ( (DrawContext->TimeOutSeconds > 0)
&& (GetTimeInNanoSecond (NewLastTsc - LoopStartTsc) >= DrawContext->TimeOutSeconds * 1000000000ULL))
{
if (DrawContext->GuiContext->PickerContext->PickerAudioAssist) {
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OC_VOICE_OVER_AUDIO_FILE_TIMEOUT,
OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
FALSE
);
}
DrawContext->GuiContext->ReadyToBoot = TRUE;
break;
}
LastTsc = NewLastTsc;
} while (!DrawContext->ExitLoop (DrawContext, DrawContext->GuiContext));
}
VOID
GuiClearScreen (
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Pixel
)
{
GuiOutputBlt (
mOutputContext,
Pixel,
EfiBltVideoFill,
0,
0,
0,
0,
DrawContext->Screen.Width,
DrawContext->Screen.Height,
0
);
}
/// A sine approximation via a third-order approx.
/// @param x Angle (with 2^15 units/circle)
/// @return Sine value (Q12)
STATIC
INT32
isin_S3 (
IN INT32 x
)
{
//
// S(x) = x * ( (3<<p) - (x*x>>r) ) >> s
// n : Q-pos for quarter circle 13
// A : Q-pos for output 12
// p : Q-pos for parentheses intermediate 15
// r = 2n-p 11
// s = A-1-p-n 17
//
STATIC CONST INT32 n = 13;
STATIC CONST INT32 p = 15;
STATIC CONST INT32 r = 11;
STATIC CONST INT32 s = 17;
x = x << (30 - n); // shift to full s32 range (Q13->Q30)
if ((x ^ (x << 1)) < 0) {
// test for quadrant 1 or 2
x = (1 << 31) - x;
}
x = x >> (30 - n);
return x * ((3 << p) - (x * x >> r)) >> s;
}
UINT32
GuiGetInterpolatedValue (
IN CONST GUI_INTERPOLATION *Interpol,
IN UINT64 CurrentTime
)
{
INT32 AnimTime;
UINT32 DeltaTime;
ASSERT (Interpol != NULL);
ASSERT (Interpol->Duration > 0);
STATIC CONST UINT32 InterpolFpTimeFactor = 1U << 12U;
if (CurrentTime <= Interpol->StartTime) {
return Interpol->StartValue;
}
DeltaTime = (UINT32)(CurrentTime - Interpol->StartTime);
if (DeltaTime >= Interpol->Duration) {
return Interpol->EndValue;
}
AnimTime = (INT32)DivU64x64Remainder ((UINT64)InterpolFpTimeFactor * DeltaTime, Interpol->Duration, NULL);
if (Interpol->Type == GuiInterpolTypeSmooth) {
//
// One InterpolFpTimeFactor unit corresponds to 45 degrees in the unit circle. Divide
// the time by two because the integral of sin from 0 to Pi is equal to 2,
// i.e. double speed.
//
AnimTime = isin_S3 (4 * AnimTime / 2);
//
// FP-square to further smoothen the animation.
//
AnimTime = (AnimTime * AnimTime) / InterpolFpTimeFactor;
} else {
ASSERT (Interpol->Type == GuiInterpolTypeLinear);
}
return (Interpol->EndValue * AnimTime
+ (Interpol->StartValue * (InterpolFpTimeFactor - AnimTime)))
/ InterpolFpTimeFactor;
}