mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1460 lines
42 KiB
C
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;
|
|
}
|