GNU-EFI GOP How prevent flickering graphics when redrawing ?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
gomidas
Posts: 15
Joined: Wed Nov 25, 2020 9:28 pm

GNU-EFI GOP How prevent flickering graphics when redrawing ?

Post by gomidas »

Hello, I am drawing cursor for my os with GOP. First I clear entire screen to a color then I draw my cursor. I use a header file called gGraphicsRenderer.h for my GOP.


GraphicsRenderer.h

Code: Select all

#pragma once
#include "efi_reference.h"
#include "gColor.h"
#include <stdbool.h>

//DEFINED
#define PIXEL_SCALE 1

//GRAPHICS
static EFI_STATUS GraphicsStatus;

static EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
static EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE gopMode;
static EFI_HANDLE* handles = NULL;
static UINTN        HandleCount;
static EFI_HANDLE* GraphicsHandleBuffer = NULL;
static EFI_GRAPHICS_OUTPUT_BLT_PIXEL Square[PIXEL_SCALE];
static EFI_GRAPHICS_OUTPUT_BLT_PIXEL Backup[PIXEL_SCALE];

static EFI_EDID_DISCOVERED_PROTOCOL edid;

static EFI_GUID GraphicsOutputProtocolGUID = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;


static EFI_UNICODE_COLLATION_PROTOCOL* mUnicodeCollation;
static EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* info;
static UINTN SizeOfInfo;
static UINTN ModeCount;

static void RunGraphics() {
	GraphicsStatus = gBS->LocateProtocol(&GraphicsOutputProtocolGUID, NULL, (void**)&gop);

	if (EFI_ERROR(GraphicsStatus) || gop == NULL)
	{
		gST->ConOut->OutputString(gST->ConOut, L"Failed to init gfx!\r\n");
		return GraphicsStatus;
	}
	
	gST->ConOut->ClearScreen(gST->ConOut);

	// Locate all instances of GOP
	GraphicsStatus = gBS->LocateHandleBuffer(ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &HandleCount, &GraphicsHandleBuffer);
	if (EFI_ERROR(GraphicsStatus)) {
		DEBUG((-1, "ShowStatus: Graphics output protocol not found\n"));
		return EFI_UNSUPPORTED;
	}

	ModeCount = gop->Mode->MaxMode - 1;

	//gop->Mode->Mode
	GraphicsStatus = gop->SetMode(gop, ModeCount);
	if (EFI_ERROR(GraphicsStatus)) {
		gST->ConOut->OutputString(gST->ConOut, L"Failed to set default mode!\r\n");
		return GraphicsStatus;
	}
	GraphicsStatus = gBS->LocateProtocol(&gEfiUnicodeCollationProtocolGuid, NULL, &mUnicodeCollation);

	if (EFI_ERROR(GraphicsStatus))
	{
		Print(L"Can't Locate Protocol\n");
		return GraphicsStatus;
	}

	gop->QueryMode(gop, gop->Mode->Mode, &SizeOfInfo, &info);
	
}

static void DrawCursorClassic(UINTN posX, UINTN posY, RGB* CursorColor)
{
	for (UINTN i = 0; i < PIXEL_SCALE; i++)
	{
		Square[i].Blue = CursorColor->b;
		Square[i].Green = CursorColor->g;
		Square[i].Red = CursorColor->r;
		Square[i].Reserved = 0x00;
	}
	for (int i = 0; i < 16; i++)
	{
		GraphicsStatus = gop->Blt(gop, &Square, EfiBltVideoFill, 0, 0, (UINTN)(posX+i), (UINTN)(posY + i), 8, 8, 0);
	}
}

static void ClearScreen(RGB* Color)
{
	for (UINTN i = 0; i < PIXEL_SCALE; i++)
	{
		Square[i].Red = Color->r;
		Square[i].Green = Color->g;
		Square[i].Blue = Color->b;
		Square[i].Reserved = 0x00;
	}

	GraphicsStatus = gop->Blt(gop, &Square, EfiBltVideoFill, 0, 0, (UINTN)(0), (UINTN)(0), info->HorizontalResolution, info->VerticalResolution, 0);
	
}

static void DrawScene() {

	for (UINTN i = 0; i < HandleCount; i++)
	{
		// Handle protocol
		GraphicsStatus = gBS->HandleProtocol(GraphicsHandleBuffer[i], &gEfiGraphicsOutputProtocolGuid, (VOID**)&gop);

		if (EFI_ERROR(GraphicsStatus))
		{
			DEBUG((-1, "ShowStatus: gBS->HandleProtocol[%d] returned %r\n", i, Status));
			continue;
		}

		// Backup current image
		gop->Blt(gop, Backup, EfiBltVideoToBltBuffer, 0, 0, 0, 0, PIXEL_SCALE, PIXEL_SCALE, 0);

		// Draw the status square
		gop->Blt(gop, Square, EfiBltBufferToVideo, 0, 0, 0, 0, PIXEL_SCALE, PIXEL_SCALE, 0);

		gBS->Stall(0);

		// Restore the backup
		gop->Blt(gop, Backup, EfiBltBufferToVideo, 0, 0, 0, 0, PIXEL_SCALE, PIXEL_SCALE, 0);
	}
}

static void PrintUnicodeText(CHAR16 *unicodeText, int fontSize, bool IsUpperCase)
{
	mUnicodeCollation->SupportedLanguages = "en-us;zh-Hant;ar-EG";	
	Print(L"%s", unicodeText);

}

main.c

Code: Select all

// {...
//Keyboard
	Events[1] = gST->ConIn->WaitForKey;
	Events[0] = mouse->WaitForInput;

	RunGraphics();

	setCursorPosition(&cursor, info->HorizontalResolution/2, info->VerticalResolution / 2, info->HorizontalResolution, info->VerticalResolution);

	RGB cursor_COLOR = {255,255,255};
	RGB background_COLOR = { 0,0,0 };

	while (1)
	{
		ClearScreen(&background_COLOR);
		DrawCursorClassic(cursor.Position.X, cursor.Position.Y, &cursor_COLOR);
		DrawScene(0);

		UINTN index;
		Status = gBS->WaitForEvent(2, &Events, &index);
		// INDEX 1 IS KEYBOARD MESSAGE
		if (index == 1)
		{
			Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

			if ((UINTN)Key.ScanCode == SCAN_ESC)
			{
				if(ESC_KEY_DOWN_COUNT == 0)
				{
					SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
					SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
					return EFI_SUCCESS;
				}
				ESC_KEY_DOWN_COUNT--;
			}
			else if ((UINTN)Key.ScanCode == SCAN_LEFT){
				setCursorPosition(&cursor, cursor.Position.X -1, cursor.Position.Y, info->HorizontalResolution, info->VerticalResolution);

			}
			else
			{
				ESC_KEY_DOWN_COUNT = 2;
			}
		}
		else if (index == 0)
		{
			Status = mouse->GetState(mouse, &State);

			Print(L"X:%d Y:%d Z:%d L:%d R:%d\n",
				State.RelativeMovementX,
				State.RelativeMovementY,
				State.RelativeMovementZ,
				State.LeftButton,
				State.RightButton
			);

			setCursorPosition(&cursor, cursor.Position.X + State.RelativeMovementX, cursor.Position.Y + State.RelativeMovementY, info->HorizontalResolution, info->VerticalResolution);
		}

	}
	
	SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
	
	gClose();
	collectGarbage();
	return EFI_SUCCESS;

#if defined(_DEBUG)
	// If running in debug mode, use the EFI shut down call to close QEMU
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
#endif

	return EFI_SUCCESS;
}
I see barely visible flickering when moving my cursor with mouse or arrow keys on keyboard.

What should I do to prevent this issue ?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: GNU-EFI GOP How prevent flickering graphics when redrawi

Post by bzt »

gomidas wrote:I see barely visible flickering when moving my cursor with mouse or arrow keys on keyboard.

What should I do to prevent this issue ?
It's because you clear the screen. Don't do that. Instead, store the buffer under the cursor. So your logic should be
1. put saved buffer at old cursor position (buffer -> screen, essentially hide cursor)
2. update cursor position
3. save buffer at new cursor position (screen -> buffer)
4. draw the cursor

Repeat this in each iteration. This only writes 2 times the cursor's width * height pixels, no more. If you have a clear screen, and your screen is 1024 x 768 and your cursor is 16 x 16, then you'd have to write 786688 pixels for every cursor movements. That's a lot. On the other hand with the cursor backbuffer technique, you'd only need to write 512 pixels. That's a lot faster, and you'd see no flickering.

There's only small drawback though, whenever you update the screen with new data, don't forget to update the cursor's backbuffer too (or just repeat the logic above, but instead of updating the cursor position at step 2, modify the screen there). It also helps if you implement step 1 as function HideCursor() and steps 3 and 4 as ShowCursor(), will make your code more readable.

Cheers,
bzt
gomidas
Posts: 15
Joined: Wed Nov 25, 2020 9:28 pm

Re: GNU-EFI GOP How prevent flickering graphics when redrawi

Post by gomidas »

bzt wrote: 1. put saved buffer at old cursor position (buffer -> screen, essentially hide cursor)
2. update cursor position
3. save buffer at new cursor position (screen -> buffer)
4. draw the cursor
Thanks for your interest, with buffer->screen and screen->buffer you mean EfiBltVideoToBltBuffer, EfiBltBufferToVideo ?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: GNU-EFI GOP How prevent flickering graphics when redrawi

Post by bzt »

gomidas wrote:Thanks for your interest, with buffer->screen and screen->buffer you mean EfiBltVideoToBltBuffer, EfiBltBufferToVideo ?
Yes, for example. Or any other blitting technique you might use (I'm thinking mostly in terms of kernels, and sadly a kernel can't use GOP->Blt. But for your app that's okay, and probably the best way to do it.)

Cheers,
bzt
gomidas
Posts: 15
Joined: Wed Nov 25, 2020 9:28 pm

Re: GNU-EFI GOP How prevent flickering graphics when redrawi

Post by gomidas »

bzt wrote:
gomidas wrote:Thanks for your interest, with buffer->screen and screen->buffer you mean EfiBltVideoToBltBuffer, EfiBltBufferToVideo ?
Yes, for example. Or any other blitting technique you might use (I'm thinking mostly in terms of kernels, and sadly a kernel can't use GOP->Blt. But for your app that's okay, and probably the best way to do it.)

Cheers,
bzt
Here is my result: https://www.youtube.com/watch?v=F-zNHCR ... e=youtu.be

Flickering is completely gone. Only, I see some black lines when moved it too fast. However, I see those at BIOS interface too when I move bios cursor.
Post Reply