#include "OpenCanopy.h"
#include "GuiIo.h"
#include "GuiApp.h"
#include "Views/BootPicker.h"
typedef struct {
UINT32 MinX;
UINT32 MinY;
UINT32 MaxX;
UINT32 MaxY;
} GUI_DRAW_REQUEST;
//
// I/O contexts
//
STATIC GUI_OUTPUT_CONTEXT *mOutputContext = NULL;
STATIC GUI_POINTER_CONTEXT *mPointerContext = NULL;
STATIC GUI_KEY_CONTEXT *mKeyContext = NULL;
//
// Screen buffer information
//
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mScreenBuffer = NULL;
STATIC UINT32 mScreenBufferDelta = 0;
STATIC GUI_SCREEN_CURSOR mScreenViewCursor = { 0, 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[4] = { { 0 } };
//
// Disk label palette.
//
STATIC
CONST UINT8
gAppleDiskLabelImagePalette[256] = {
[0x00] = 255,
[0xf6] = 238,
[0xf7] = 221,
[0x2a] = 204,
[0xf8] = 187,
[0xf9] = 170,
[0x55] = 153,
[0xfa] = 136,
[0xfb] = 119,
[0x80] = 102,
[0xfc] = 85,
[0xfd] = 68,
[0xab] = 51,
[0xfe] = 34,
[0xff] = 17,
[0xd6] = 0
};
BOOLEAN
GuiClipChildBounds (
IN INT64 ChildOffset,
IN UINT32 ChildLength,
IN OUT UINT32 *ReqOffset,
IN OUT UINT32 *ReqLength
)
{
UINT32 PosChildOffset;
UINT32 OffsetDelta;
UINT32 NewOffset;
UINT32 NewLength;
ASSERT (ReqOffset != NULL);
ASSERT (ReqLength != NULL);
if (ChildLength == 0) {
return FALSE;
}
if (ChildOffset >= 0) {
PosChildOffset = (UINT32)ChildOffset;
} else {
if ((INT64)ChildLength - (-ChildOffset) <= 0) {
return FALSE;
}
PosChildOffset = 0;
ChildLength = (UINT32)(ChildLength - (-ChildOffset));
}
NewOffset = *ReqOffset;
NewLength = *ReqLength;
if (NewOffset >= PosChildOffset) {
//
// The requested offset starts within or past the child.
//
OffsetDelta = (NewOffset - PosChildOffset);
if (ChildLength <= OffsetDelta) {
//
// The requested offset starts past the child.
//
return FALSE;
}
//
// The requested offset starts within the child.
//
NewOffset -= PosChildOffset;
} else {
//
// The requested offset ends within or before the child.
//
OffsetDelta = (PosChildOffset - NewOffset);
if (NewLength <= OffsetDelta) {
//
// The requested offset ends before the child.
//
return FALSE;
}
//
// The requested offset ends within the child.
//
NewOffset = 0;
NewLength -= OffsetDelta;
}
if (ChildOffset < 0) {
NewOffset = (UINT32)(NewOffset + (-ChildOffset));
}
*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 BOOLEAN RequestDraw
)
{
BOOLEAN Result;
LIST_ENTRY *ChildEntry;
GUI_OBJ_CHILD *Child;
UINT32 ChildDrawOffsetX;
UINT32 ChildDrawOffsetY;
UINT32 ChildDrawWidth;
UINT32 ChildDrawHeight;
ASSERT (This != NULL);
ASSERT (This->Width > OffsetX);
ASSERT (This->Height > OffsetY);
ASSERT (DrawContext != NULL);
for (
ChildEntry = GetPreviousNode (&This->Children, &This->Children);
!IsNull (&This->Children, ChildEntry);
ChildEntry = GetPreviousNode (&This->Children, ChildEntry)
) {
Child = BASE_CR (ChildEntry, GUI_OBJ_CHILD, Link);
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;
}
ASSERT (Child->Obj.Draw != NULL);
Child->Obj.Draw (
&Child->Obj,
DrawContext,
Context,
BaseX + Child->Obj.OffsetX,
BaseY + Child->Obj.OffsetY,
ChildDrawOffsetX,
ChildDrawOffsetY,
ChildDrawWidth,
ChildDrawHeight,
RequestDraw
);
}
}
GUI_OBJ *
GuiObjDelegatePtrEvent (
IN OUT GUI_OBJ *This,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN BOOT_PICKER_GUI_CONTEXT *Context,
IN GUI_PTR_EVENT Event,
IN INT64 BaseX,
IN INT64 BaseY,
IN INT64 OffsetX,
IN INT64 OffsetY
)
{
GUI_OBJ *Obj;
LIST_ENTRY *ChildEntry;
GUI_OBJ_CHILD *Child;
ASSERT (This != NULL);
ASSERT (This->Width > OffsetX);
ASSERT (This->Height > OffsetY);
ASSERT (DrawContext != NULL);
for (
ChildEntry = GetFirstNode (&This->Children);
!IsNull (&This->Children, ChildEntry);
ChildEntry = GetNextNode (&This->Children, ChildEntry)
) {
Child = BASE_CR (ChildEntry, GUI_OBJ_CHILD, Link);
if (OffsetX < Child->Obj.OffsetX
|| OffsetX >= Child->Obj.OffsetX + Child->Obj.Width
|| OffsetY < Child->Obj.OffsetY
|| OffsetY >= Child->Obj.OffsetY + Child->Obj.Height) {
continue;
}
ASSERT (Child->Obj.PtrEvent != NULL);
Obj = Child->Obj.PtrEvent (
&Child->Obj,
DrawContext,
Context,
Event,
BaseX + Child->Obj.OffsetX,
BaseY + Child->Obj.OffsetY,
OffsetX - Child->Obj.OffsetX,
OffsetY - Child->Obj.OffsetY
);
if (Obj != NULL) {
return Obj;
}
}
return NULL;
}
#define RGB_APPLY_OPACITY(Rgba, Opacity) \
(((Rgba) * (Opacity)) / 0xFF)
#define RGB_ALPHA_BLEND(Back, Front, InvFrontOpacity) \
((Front) + RGB_APPLY_OPACITY (InvFrontOpacity, Back))
VOID
GuiBlendPixel (
IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BackPixel,
IN CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *FrontPixel,
IN UINT8 Opacity
)
{
UINT8 CombOpacity;
UINT8 InvFrontOpacity;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL OpacFrontPixel;
CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *FinalFrontPixel;
//
// FIXME: Optimise with SIMD or such.
// qt_blend_argb32_on_argb32 in QT
//
ASSERT (BackPixel != NULL);
ASSERT (FrontPixel != NULL);
if (FrontPixel->Reserved == 0) {
return;
}
if (FrontPixel->Reserved == 0xFF) {
if (Opacity == 0xFF) {
BackPixel->Blue = FrontPixel->Blue;
BackPixel->Green = FrontPixel->Green;
BackPixel->Red = FrontPixel->Red;
BackPixel->Reserved = FrontPixel->Reserved;
return;
}
CombOpacity = Opacity;
} else {
CombOpacity = RGB_APPLY_OPACITY (FrontPixel->Reserved, Opacity);
}
if (CombOpacity == 0) {
return;
} else if (CombOpacity == FrontPixel->Reserved) {
FinalFrontPixel = FrontPixel;
} else {
OpacFrontPixel.Reserved = CombOpacity;
OpacFrontPixel.Blue = RGB_APPLY_OPACITY (FrontPixel->Blue, Opacity);
OpacFrontPixel.Green = RGB_APPLY_OPACITY (FrontPixel->Green, Opacity);
OpacFrontPixel.Red = RGB_APPLY_OPACITY (FrontPixel->Red, Opacity);
FinalFrontPixel = &OpacFrontPixel;
}
InvFrontOpacity = (0xFF - CombOpacity);
BackPixel->Blue = RGB_ALPHA_BLEND (
BackPixel->Blue,
FinalFrontPixel->Blue,
InvFrontOpacity
);
BackPixel->Green = RGB_ALPHA_BLEND (
BackPixel->Green,
FinalFrontPixel->Green,
InvFrontOpacity
);
BackPixel->Red = RGB_ALPHA_BLEND (
BackPixel->Red,
FinalFrontPixel->Red,
InvFrontOpacity
);
if (BackPixel->Reserved != 0xFF) {
BackPixel->Reserved = RGB_ALPHA_BLEND (
BackPixel->Reserved,
CombOpacity,
InvFrontOpacity
);
}
}
VOID
GuiDrawToBuffer (
IN CONST GUI_IMAGE *Image,
IN UINT8 Opacity,
IN BOOLEAN Fill,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN INT64 BaseX,
IN INT64 BaseY,
IN UINT32 OffsetX,
IN UINT32 OffsetY,
IN UINT32 Width,
IN UINT32 Height,
IN BOOLEAN RequestDraw
)
{
UINT32 PosBaseX;
UINT32 PosBaseY;
UINT32 PosOffsetX;
UINT32 PosOffsetY;
UINT32 RowIndex;
UINT32 SourceRowOffset;
UINT32 TargetRowOffset;
UINT32 SourceColumnOffset;
UINT32 TargetColumnOffset;
CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *SourcePixel;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *TargetPixel;
GUI_DRAW_REQUEST ThisReq;
UINTN Index;
UINT32 ThisArea;
UINT32 ReqWidth;
UINT32 ReqHeight;
UINT32 ReqArea;
UINT32 CombMinX;
UINT32 CombMaxX;
UINT32 CombMinY;
UINT32 CombMaxY;
UINT32 CombWidth;
UINT32 CombHeight;
UINT32 CombArea;
UINT32 OverMinX;
UINT32 OverMaxX;
UINT32 OverMinY;
UINT32 OverMaxY;
UINT32 OverArea;
UINT32 OverWidth;
UINT32 OverHeight;
UINT32 ActualArea;
ASSERT (Image != NULL);
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->Screen != NULL);
ASSERT (BaseX + OffsetX >= 0);
ASSERT (BaseY + OffsetY >= 0);
//
// Only draw the onscreen parts.
//
if (BaseX >= 0) {
PosBaseX = (UINT32)BaseX;
PosOffsetX = OffsetX;
} else {
ASSERT (BaseX + OffsetX + Width >= 0);
PosBaseX = 0;
if (OffsetX - (-BaseX) >= 0) {
PosOffsetX = (UINT32)(OffsetX - (-BaseX));
} else {
PosOffsetX = 0;
Width = (UINT32)(Width - (-(OffsetX - (-BaseX))));
}
}
if (BaseY >= 0) {
PosBaseY = (UINT32)BaseY;
PosOffsetY = OffsetY;
} else {
ASSERT (BaseY + OffsetY + Height >= 0);
PosBaseY = 0;
if (OffsetY - (-BaseY) >= 0) {
PosOffsetY = (UINT32)(OffsetY - (-BaseY));
} else {
PosOffsetY = 0;
Height = (UINT32)(Height - (-(OffsetY - (-BaseY))));
}
}
if (!Fill) {
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);
}
//
// Crop to the screen's dimensions.
//
ASSERT (DrawContext->Screen->Width >= PosBaseX + PosOffsetX);
ASSERT (DrawContext->Screen->Height >= PosBaseY + PosOffsetY);
Width = MIN (Width, DrawContext->Screen->Width - (PosBaseX + PosOffsetX));
Height = MIN (Height, DrawContext->Screen->Height - (PosBaseY + PosOffsetY));
if (Width == 0 || Height == 0) {
return;
}
ASSERT (Image->Buffer != NULL);
if (!Fill) {
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
SourceRowOffset = OffsetY * Image->Width,
TargetRowOffset = (PosBaseY + PosOffsetY) * DrawContext->Screen->Width;
RowIndex < Height;
++RowIndex,
SourceRowOffset += Image->Width,
TargetRowOffset += DrawContext->Screen->Width
) {
//
// Blend the row pixel-by-pixel.
//
for (
TargetColumnOffset = PosOffsetX, SourceColumnOffset = OffsetX;
TargetColumnOffset < PosOffsetX + Width;
++TargetColumnOffset, ++SourceColumnOffset
) {
TargetPixel = &mScreenBuffer[TargetRowOffset + PosBaseX + TargetColumnOffset];
SourcePixel = &Image->Buffer[SourceRowOffset + SourceColumnOffset];
GuiBlendPixel (TargetPixel, SourcePixel, Opacity);
}
}
} else {
//
// Iterate over each row of the request.
//
for (
RowIndex = 0,
TargetRowOffset = (PosBaseY + PosOffsetY) * DrawContext->Screen->Width;
RowIndex < Height;
++RowIndex,
TargetRowOffset += DrawContext->Screen->Width
) {
//
// Blend the row pixel-by-pixel with Source's (0,0).
//
for (
TargetColumnOffset = PosOffsetX;
TargetColumnOffset < PosOffsetX + Width;
++TargetColumnOffset
) {
TargetPixel = &mScreenBuffer[TargetRowOffset + PosBaseX + TargetColumnOffset];
GuiBlendPixel (TargetPixel, &Image->Buffer[0], Opacity);
}
}
}
if (RequestDraw) {
//
// Update the coordinates of the smallest rectangle covering all changes.
//
ThisReq.MinX = PosBaseX + PosOffsetX;
ThisReq.MinY = PosBaseY + PosOffsetY;
ThisReq.MaxX = PosBaseX + PosOffsetX + Width - 1;
ThisReq.MaxY = PosBaseY + PosOffsetY + Height - 1;
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.
//
ReqWidth = mDrawRequests[Index].MaxX - mDrawRequests[Index].MinX + 1;
ReqHeight = mDrawRequests[Index].MaxY - mDrawRequests[Index].MinY + 1;
ReqArea = ReqWidth * ReqHeight;
if (mDrawRequests[Index].MinX < ThisReq.MinX) {
CombMinX = mDrawRequests[Index].MinX;
OverMinX = ThisReq.MinX;
} else {
CombMinX = ThisReq.MinX;
OverMinX = mDrawRequests[Index].MinX;
}
if (mDrawRequests[Index].MaxX > ThisReq.MaxX) {
CombMaxX = mDrawRequests[Index].MaxX;
OverMaxX = ThisReq.MaxX;
} else {
CombMaxX = ThisReq.MaxX;
OverMaxX = mDrawRequests[Index].MaxX;
}
if (mDrawRequests[Index].MinY < ThisReq.MinY) {
CombMinY = mDrawRequests[Index].MinY;
OverMinY = ThisReq.MinY;
} else {
CombMinY = ThisReq.MinY;
OverMinY = mDrawRequests[Index].MinY;
}
if (mDrawRequests[Index].MaxY > ThisReq.MaxY) {
CombMaxY = mDrawRequests[Index].MaxY;
OverMaxY = ThisReq.MaxY;
} else {
CombMaxY = ThisReq.MaxY;
OverMaxY = mDrawRequests[Index].MaxY;
}
CombWidth = CombMaxX - CombMinX + 1;
CombHeight = CombMaxY - CombMinY + 1;
CombArea = CombWidth * CombHeight;
OverArea = 0;
if (OverMinX <= OverMaxX && OverMinY <= OverMaxY) {
OverWidth = OverMaxX - OverMinX + 1;
OverHeight = OverMaxY - OverMinY + 1;
OverArea = OverWidth * OverHeight;
}
ActualArea = ThisArea + ReqArea - OverArea;
//
// Two requests are merged when their combined actual draw area is at
// least 3/4 of the area needed to draw both at once.
//
if (4 * ActualArea >= 3 * CombArea) {
mDrawRequests[Index].MinX = CombMinX;
mDrawRequests[Index].MaxX = CombMaxX;
mDrawRequests[Index].MinY = CombMinY;
mDrawRequests[Index].MaxY = CombMaxY;
return;
}
}
if (mNumValidDrawReqs >= ARRAY_SIZE (mDrawRequests)) {
ASSERT (FALSE);
return;
}
CopyMem (&mDrawRequests[mNumValidDrawReqs], &ThisReq, sizeof (ThisReq));
++mNumValidDrawReqs;
}
}
VOID
GuiDrawScreen (
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN INT64 X,
IN INT64 Y,
IN UINT32 Width,
IN UINT32 Height,
IN BOOLEAN RequestDraw
)
{
UINT32 PosX;
UINT32 PosY;
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->Screen != NULL);
//
// Only draw the onscreen parts.
//
if (X >= 0) {
PosX = (UINT32)X;
} else {
if (X + Width <= 0) {
return;
}
Width = (UINT32)(Width - (-X));
PosX = 0;
}
if (Y >= 0) {
PosY = (UINT32)Y;
} else {
if (Y + Height <= 0) {
return;
}
Height = (UINT32)(Height - (-Y));
PosY = 0;
}
if (PosX >= DrawContext->Screen->Width
|| PosY >= DrawContext->Screen->Height) {
return;
}
Width = MIN (Width, DrawContext->Screen->Width - PosX);
Height = MIN (Height, DrawContext->Screen->Height - PosY);
if (Width == 0 || Height == 0) {
return;
}
ASSERT (DrawContext->Screen->OffsetX == 0);
ASSERT (DrawContext->Screen->OffsetY == 0);
ASSERT (DrawContext->Screen->Draw != NULL);
DrawContext->Screen->Draw (
DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
0,
0,
PosX,
PosY,
Width,
Height,
RequestDraw
);
}
VOID
GuiRedrawObject (
IN OUT GUI_OBJ *This,
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN INT64 BaseX,
IN INT64 BaseY,
IN BOOLEAN RequestDraw
)
{
ASSERT (This != NULL);
ASSERT (DrawContext != NULL);
GuiDrawScreen (
DrawContext,
BaseX,
BaseY,
This->Width,
This->Height,
RequestDraw
);
}
VOID
GuiRedrawPointer (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
STATIC UINT32 CursorOldX = 0;
STATIC UINT32 CursorOldY = 0;
STATIC UINT32 CursorOldWidth = 0;
STATIC UINT32 CursorOldHeight = 0;
STATIC CONST GUI_IMAGE *CursorOldImage = NULL;
CONST GUI_IMAGE *CursorImage;
BOOLEAN RequestDraw;
UINT32 MinX;
UINT32 DeltaX;
UINT32 MinY;
UINT32 DeltaY;
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->GetCursorImage != NULL);
CursorImage = DrawContext->GetCursorImage (
&mScreenViewCursor,
DrawContext->GuiContext
);
ASSERT (CursorImage != NULL);
RequestDraw = FALSE;
if (mScreenViewCursor.X != CursorOldX || mScreenViewCursor.Y != CursorOldY) {
//
// Redraw the cursor when it has been moved.
//
RequestDraw = TRUE;
} else if (CursorImage != CursorOldImage) {
//
// Redraw the cursor if its image has changed.
//
RequestDraw = TRUE;
} else if (mNumValidDrawReqs == 0) {
//
// Redraw the cursor if nothing else is drawn to always invoke GOP for a
// more consistent framerate.
//
RequestDraw = TRUE;
}
//
// Always drawing the cursor to the buffer increases consistency and is less
// error-prone to situational hiding.
//
// Restore the rectangle previously covered by the cursor.
// Cover the area of the new cursor too and do not request a draw of the new
// cursor to not need to merge the requests later.
//
if (CursorOldX < mScreenViewCursor.X) {
MinX = CursorOldX;
DeltaX = mScreenViewCursor.X - CursorOldX;
} else {
MinX = mScreenViewCursor.X;
DeltaX = CursorOldX - mScreenViewCursor.X;
}
if (CursorOldY < mScreenViewCursor.Y) {
MinY = CursorOldY;
DeltaY = mScreenViewCursor.Y - CursorOldY;
} else {
MinY = mScreenViewCursor.Y;
DeltaY = CursorOldY - mScreenViewCursor.Y;
}
GuiDrawScreen (
DrawContext,
MinX,
MinY,
MAX (CursorOldWidth, CursorImage->Width) + DeltaX,
MAX (CursorOldHeight, CursorImage->Height) + DeltaY,
RequestDraw
);
GuiDrawToBuffer (
CursorImage,
0xFF,
FALSE,
DrawContext,
mScreenViewCursor.X,
mScreenViewCursor.Y,
0,
0,
CursorImage->Width,
CursorImage->Height,
FALSE
);
if (RequestDraw) {
CursorOldX = mScreenViewCursor.X;
CursorOldY = mScreenViewCursor.Y;
CursorOldWidth = CursorImage->Width;
CursorOldHeight = CursorImage->Height;
CursorOldImage = CursorImage;
} else {
ASSERT (CursorOldX == mScreenViewCursor.X);
ASSERT (CursorOldY == mScreenViewCursor.Y);
ASSERT (CursorOldWidth == CursorImage->Width);
ASSERT (CursorOldHeight == CursorImage->Height);
ASSERT (CursorOldImage == CursorImage);
}
}
/**
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
)
{
EFI_TPL OldTpl;
UINTN NumValidDrawReqs;
UINTN Index;
UINT64 EndTsc;
UINT64 DeltaTsc;
BOOLEAN Interrupts;
ASSERT (DrawContext != NULL);
ASSERT (DrawContext->Screen != NULL);
if (mPointerContext != NULL) {
GuiRedrawPointer (DrawContext);
}
NumValidDrawReqs = mNumValidDrawReqs;
ASSERT (NumValidDrawReqs <= ARRAY_SIZE (mDrawRequests));
mNumValidDrawReqs = 0;
for (Index = 0; Index < NumValidDrawReqs; ++Index) {
ASSERT (mDrawRequests[Index].MaxX >= mDrawRequests[Index].MinX);
ASSERT (mDrawRequests[Index].MaxY >= mDrawRequests[Index].MinY);
//
// Set MaxX/Y to Width and Height as the requests are invalidated anyway.
//
mDrawRequests[Index].MaxX -= mDrawRequests[Index].MinX - 1;
mDrawRequests[Index].MaxY -= mDrawRequests[Index].MinY - 1;
}
//
// Raise the TPL to not interrupt timing or flushing.
//
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
Interrupts = SaveAndDisableInterrupts ();
EndTsc = AsmReadTsc ();
DeltaTsc = EndTsc - mStartTsc;
if (DeltaTsc < mDeltaTscTarget) {
EndTsc = InternalCpuDelayTsc (mDeltaTscTarget - DeltaTsc);
}
for (Index = 0; Index < NumValidDrawReqs; ++Index) {
//
// Due to above's loop, MaxX/Y correspond to Width and Height here.
//
GuiOutputBlt (
mOutputContext,
mScreenBuffer,
EfiBltBufferToVideo,
mDrawRequests[Index].MinX,
mDrawRequests[Index].MinY,
mDrawRequests[Index].MinX,
mDrawRequests[Index].MinY,
mDrawRequests[Index].MaxX,
mDrawRequests[Index].MaxY,
mScreenBufferDelta
);
}
if (Interrupts) {
EnableInterrupts ();
}
gBS->RestoreTPL (OldTpl);
//
// 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);
ASSERT (DrawContext->Screen != NULL);
mStartTsc = AsmReadTsc ();
GuiRedrawObject (DrawContext->Screen, DrawContext, 0, 0, TRUE);
GuiFlushScreen (DrawContext);
}
EFI_STATUS
GuiLibConstruct (
IN OC_PICKER_CONTEXT *PickerContext,
IN UINT32 CursorDefaultX,
IN UINT32 CursorDefaultY
)
{
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
mOutputContext = GuiOutputConstruct ();
if (mOutputContext == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise output\n"));
return EFI_UNSUPPORTED;
}
OutputInfo = GuiOutputGetInfo (mOutputContext);
ASSERT (OutputInfo != NULL);
CursorDefaultX = MIN (CursorDefaultX, OutputInfo->HorizontalResolution - 1);
CursorDefaultY = MIN (CursorDefaultY, OutputInfo->VerticalResolution - 1);
if ((PickerContext->PickerAttributes & OC_ATTR_USE_POINTER_CONTROL) != 0) {
mPointerContext = GuiPointerConstruct (
PickerContext,
CursorDefaultX,
CursorDefaultY,
OutputInfo->HorizontalResolution,
OutputInfo->VerticalResolution
);
if (mPointerContext == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise pointer\n"));
}
}
mKeyContext = GuiKeyConstruct (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);
mScreenViewCursor.X = CursorDefaultX;
mScreenViewCursor.Y = CursorDefaultY;
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 OUT GUI_OBJ *Screen,
IN GUI_CURSOR_GET_IMAGE GetCursorImage,
IN GUI_EXIT_LOOP ExitLoop,
IN BOOT_PICKER_GUI_CONTEXT *GuiContext
)
{
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
ASSERT (DrawContext != NULL);
ASSERT (Screen != NULL);
ASSERT (GetCursorImage != NULL);
ASSERT (ExitLoop != NULL);
OutputInfo = GuiOutputGetInfo (mOutputContext);
ASSERT (OutputInfo != NULL);
Screen->Width = OutputInfo->HorizontalResolution;
Screen->Height = OutputInfo->VerticalResolution;
DrawContext->Screen = Screen;
DrawContext->GetCursorImage = GetCursorImage;
DrawContext->ExitLoop = ExitLoop;
DrawContext->GuiContext = GuiContext;
InitializeListHead (&DrawContext->Animations);
}
VOID
GuiViewDeinitialize (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
ZeroMem (DrawContext, sizeof (*DrawContext));
}
CONST GUI_SCREEN_CURSOR *
GuiViewCurrentCursor (
IN OUT GUI_DRAWING_CONTEXT *DrawContext
)
{
return &mScreenViewCursor;
}
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;
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;
while (Obj != DrawContext->Screen) {
X += Obj->OffsetX;
Y += Obj->OffsetY;
ChildObj = BASE_CR (Obj, GUI_OBJ_CHILD, Obj);
Obj = ChildObj->Parent;
ASSERT (Obj != NULL);
ASSERT (IsNodeInList (&Obj->Children, &ChildObj->Link));
}
*BaseX = X;
*BaseY = Y;
}
VOID
GuiDrawLoop (
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
IN UINT32 TimeOutSeconds
)
{
EFI_STATUS Status;
BOOLEAN Result;
INTN InputKey;
BOOLEAN Modifier;
GUI_POINTER_STATE PointerState;
GUI_OBJ *HoldObject;
INT64 HoldObjBaseX;
INT64 HoldObjBaseY;
CONST LIST_ENTRY *AnimEntry;
CONST GUI_ANIMATION *Animation;
UINT64 LoopStartTsc;
UINT64 LastTsc;
UINT64 NewLastTsc;
ASSERT (DrawContext != NULL);
mNumValidDrawReqs = 0;
HoldObject = NULL;
//
// Clear previous inputs.
//
if (mPointerContext != NULL) {
GuiPointerReset (mPointerContext);
}
GuiKeyReset (mKeyContext);
//
// Main drawing loop, time and derieve sub-frequencies as required.
//
LastTsc = LoopStartTsc = mStartTsc = AsmReadTsc ();
do {
if (mPointerContext != NULL) {
//
// Process pointer events.
//
Status = GuiPointerGetState (mPointerContext, &PointerState);
if (!EFI_ERROR (Status)) {
mScreenViewCursor.X = PointerState.X;
mScreenViewCursor.Y = PointerState.Y;
if (HoldObject == NULL && PointerState.PrimaryDown) {
HoldObject = GuiObjDelegatePtrEvent (
DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
GuiPointerPrimaryDown,
0,
0,
PointerState.X,
PointerState.Y
);
}
}
if (HoldObject != NULL) {
GuiGetBaseCoords (
HoldObject,
DrawContext,
&HoldObjBaseX,
&HoldObjBaseY
);
HoldObject->PtrEvent (
HoldObject,
DrawContext,
DrawContext->GuiContext,
!PointerState.PrimaryDown ? GuiPointerPrimaryUp : GuiPointerPrimaryHold,
HoldObjBaseX,
HoldObjBaseY,
(INT64)PointerState.X - HoldObjBaseX,
(INT64)PointerState.Y - HoldObjBaseY
);
if (!PointerState.PrimaryDown) {
HoldObject = NULL;
}
}
}
if (mKeyContext != NULL) {
//
// Process key events. Only allow one key at a time for now.
//
Status = GuiKeyRead (mKeyContext, &InputKey, &Modifier);
if (!EFI_ERROR (Status)) {
ASSERT (DrawContext->Screen->KeyEvent != NULL);
DrawContext->Screen->KeyEvent (
DrawContext->Screen,
DrawContext,
DrawContext->GuiContext,
0,
0,
InputKey,
Modifier
);
//
// If detected key press then disable menu timeout
//
TimeOutSeconds = 0;
}
}
STATIC UINT64 FrameTime = 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, FrameTime);
AnimEntry = GetNextNode (&DrawContext->Animations, AnimEntry);
if (Result) {
RemoveEntryList (&Animation->Link);
}
}
++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) {
DrawContext->GuiContext->PickerContext->PlayAudioFile (
DrawContext->GuiContext->PickerContext,
OcVoiceOverAudioFileSelected,
FALSE
);
DrawContext->GuiContext->PickerContext->PlayAudioEntry (
DrawContext->GuiContext->PickerContext,
DrawContext->GuiContext->BootEntry
);
}
}
//
// Exit early if reach timer timeout and timer isn't disabled due to key event
//
if (TimeOutSeconds > 0
&& GetTimeInNanoSecond (NewLastTsc - LoopStartTsc) >= TimeOutSeconds * 1000000000ULL) {
DrawContext->GuiContext->ReadyToBoot = TRUE;
break;
}
LastTsc = NewLastTsc;
} while (!DrawContext->ExitLoop (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
);
}
EFI_STATUS
GuiIcnsToImageIcon (
OUT GUI_IMAGE *Image,
IN VOID *IcnsImage,
IN UINT32 IcnsImageSize,
IN UINT8 Scale,
IN UINT32 MatchWidth,
IN UINT32 MatchHeight,
IN BOOLEAN AllowLess
)
{
EFI_STATUS Status;
UINT32 Offset;
UINT32 RecordLength;
UINT32 ImageSize;
UINT32 DecodedBytes;
APPLE_ICNS_RECORD *Record;
APPLE_ICNS_RECORD *RecordIT32;
APPLE_ICNS_RECORD *RecordT8MK;
ASSERT (Scale == 1 || Scale == 2);
//
// We do not need to support 'it32' 128x128 icon format,
// because Finder automatically converts the icons to PNG-based
// when assigning volume icon.
//
if (IcnsImageSize < sizeof (APPLE_ICNS_RECORD)*2) {
return EFI_INVALID_PARAMETER;
}
Record = IcnsImage;
if (Record->Type != APPLE_ICNS_MAGIC || SwapBytes32 (Record->Size) != IcnsImageSize) {
return EFI_SECURITY_VIOLATION;
}
RecordIT32 = NULL;
RecordT8MK = NULL;
Offset = sizeof (APPLE_ICNS_RECORD);
while (Offset < IcnsImageSize - sizeof (APPLE_ICNS_RECORD)) {
Record = (APPLE_ICNS_RECORD *) ((UINT8 *) IcnsImage + Offset);
RecordLength = SwapBytes32 (Record->Size);
//
// 1. Record smaller than its header and 1 32-bit word is invalid.
// 32-bit is required by some entries like IT32 (see below).
// 2. Record overflowing UINT32 is invalid.
// 3. Record larger than file size is invalid.
//
if (RecordLength < sizeof (APPLE_ICNS_RECORD) + sizeof (UINT32)
|| OcOverflowAddU32 (Offset, RecordLength, &Offset)
|| Offset > IcnsImageSize) {
return EFI_SECURITY_VIOLATION;
}
if ((Scale == 1 && Record->Type == APPLE_ICNS_IC07)
|| (Scale == 2 && Record->Type == APPLE_ICNS_IC13)) {
Status = GuiPngToImage (
Image,
Record->Data,
RecordLength - sizeof (APPLE_ICNS_RECORD),
TRUE
);
if (!EFI_ERROR (Status) && MatchWidth > 0 && MatchHeight > 0) {
if (AllowLess
? (Image->Width > MatchWidth * Scale || Image->Height > MatchWidth * Scale
|| Image->Width == 0 || Image->Height == 0)
: (Image->Width != MatchWidth * Scale || Image->Height != MatchHeight * Scale)) {
FreePool (Image->Buffer);
DEBUG ((
DEBUG_INFO,
"OCUI: Expected %dx%d, actual %dx%d, allow less: %d\n",
MatchWidth * Scale,
MatchHeight * Scale,
Image->Width,
Image->Height,
AllowLess
));
Status = EFI_UNSUPPORTED;
}
}
return Status;
}
if (Scale == 1) {
if (Record->Type == APPLE_ICNS_IT32) {
RecordIT32 = Record;
} else if (Record->Type == APPLE_ICNS_T8MK) {
RecordT8MK = Record;
}
if (RecordT8MK != NULL && RecordIT32 != NULL) {
Image->Width = MatchWidth;
Image->Height = MatchHeight;
ImageSize = (MatchWidth * MatchHeight) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
Image->Buffer = AllocateZeroPool (ImageSize);
if (Image->Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// We have to add an additional UINT32 for IT32, since it has a reserved field.
//
DecodedBytes = DecompressMaskedRLE24 (
(UINT8 *) Image->Buffer,
ImageSize,
RecordIT32->Data + sizeof (UINT32),
SwapBytes32 (RecordIT32->Size) - sizeof (APPLE_ICNS_RECORD) - sizeof (UINT32),
RecordT8MK->Data,
SwapBytes32 (RecordT8MK->Size) - sizeof (APPLE_ICNS_RECORD),
TRUE
);
if (DecodedBytes != ImageSize) {
FreePool (Image->Buffer);
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
}
}
return EFI_NOT_FOUND;
}
EFI_STATUS
GuiLabelToImage (
OUT GUI_IMAGE *Image,
IN VOID *RawData,
IN UINT32 DataLength,
IN UINT8 Scale,
IN BOOLEAN Inverted
)
{
APPLE_DISK_LABEL *Label;
UINT32 PixelIdx;
ASSERT (RawData != NULL);
ASSERT (Scale == 1 || Scale == 2);
if (DataLength < sizeof (APPLE_DISK_LABEL)) {
return EFI_INVALID_PARAMETER;
}
Label = RawData;
Image->Width = SwapBytes16 (Label->Width);
Image->Height = SwapBytes16 (Label->Height);
if (Image->Width > APPLE_DISK_LABEL_MAX_WIDTH * Scale
|| Image->Height > APPLE_DISK_LABEL_MAX_HEIGHT * Scale
|| DataLength != sizeof (APPLE_DISK_LABEL) + Image->Width * Image->Height) {
DEBUG ((DEBUG_INFO, "OCUI: Invalid label has %dx%d dims at %u size\n", Image->Width, Image->Height, DataLength));
return EFI_SECURITY_VIOLATION;
}
Image->Buffer = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) AllocatePool (
Image->Width * Image->Height * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
);
if (Image->Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
if (Inverted) {
for (PixelIdx = 0; PixelIdx < Image->Width * Image->Height; PixelIdx++) {
Image->Buffer[PixelIdx].Blue = 0;
Image->Buffer[PixelIdx].Green = 0;
Image->Buffer[PixelIdx].Red = 0;
Image->Buffer[PixelIdx].Reserved = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
}
} else {
for (PixelIdx = 0; PixelIdx < Image->Width * Image->Height; PixelIdx++) {
Image->Buffer[PixelIdx].Blue = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Green = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Red = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Reserved = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
}
}
return EFI_SUCCESS;
}
EFI_STATUS
GuiPngToImage (
OUT GUI_IMAGE *Image,
IN VOID *ImageData,
IN UINTN ImageDataSize,
IN BOOLEAN PremultiplyAlpha
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BufferWalker;
UINTN Index;
UINT8 TmpChannel;
Status = OcDecodePng (
ImageData,
ImageDataSize,
(VOID **) &Image->Buffer,
&Image->Width,
&Image->Height,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "OCUI: DecodePNG - %r\n", Status));
return Status;
}
if (PremultiplyAlpha) {
BufferWalker = Image->Buffer;
for (Index = 0; Index < (UINTN) Image->Width * Image->Height; ++Index) {
TmpChannel = (UINT8) ((BufferWalker->Blue * BufferWalker->Reserved) / 0xFF);
BufferWalker->Blue = (UINT8) ((BufferWalker->Red * BufferWalker->Reserved) / 0xFF);
BufferWalker->Green = (UINT8) ((BufferWalker->Green * BufferWalker->Reserved) / 0xFF);
BufferWalker->Red = TmpChannel;
++BufferWalker;
}
}
return EFI_SUCCESS;
}
EFI_STATUS
GuiCreateHighlightedImage (
OUT GUI_IMAGE *SelectedImage,
IN CONST GUI_IMAGE *SourceImage,
IN CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *HighlightPixel
)
{
EFI_GRAPHICS_OUTPUT_BLT_PIXEL PremulPixel;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Buffer;
UINT32 ColumnOffset;
BOOLEAN OneSet;
UINT32 FirstUnsetX;
UINT32 IndexY;
UINT32 RowOffset;
ASSERT (SelectedImage != NULL);
ASSERT (SourceImage != NULL);
ASSERT (SourceImage->Buffer != NULL);
ASSERT (HighlightPixel != NULL);
//
// The multiplication cannot wrap around because the original allocation sane.
//
Buffer = AllocateCopyPool (
SourceImage->Width * SourceImage->Height * sizeof (*SourceImage->Buffer),
SourceImage->Buffer
);
if (Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
PremulPixel.Blue = (UINT8)((HighlightPixel->Blue * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Green = (UINT8)((HighlightPixel->Green * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Red = (UINT8)((HighlightPixel->Red * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Reserved = HighlightPixel->Reserved;
for (
IndexY = 0, RowOffset = 0;
IndexY < SourceImage->Height;
++IndexY, RowOffset += SourceImage->Width
) {
FirstUnsetX = 0;
OneSet = FALSE;
for (ColumnOffset = 0; ColumnOffset < SourceImage->Width; ++ColumnOffset) {
if (SourceImage->Buffer[RowOffset + ColumnOffset].Reserved != 0) {
OneSet = TRUE;
GuiBlendPixel (
&Buffer[RowOffset + ColumnOffset],
&PremulPixel,
0xFF
);
if (FirstUnsetX != 0) {
//
// Set all fully transparent pixels between two not fully transparent
// pixels to the highlighter pixel.
//
while (FirstUnsetX < ColumnOffset) {
CopyMem (
&Buffer[RowOffset + FirstUnsetX],
&PremulPixel,
sizeof (*Buffer)
);
++FirstUnsetX;
}
FirstUnsetX = 0;
}
} else if (FirstUnsetX == 0 && OneSet) {
FirstUnsetX = ColumnOffset;
}
}
}
SelectedImage->Width = SourceImage->Width;
SelectedImage->Height = SourceImage->Height;
SelectedImage->Buffer = Buffer;
return EFI_SUCCESS;
}
/// 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<>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;
}