/* $Id: VBoxSDLTest.cpp $ */ /** @file * * VBox frontends: VBoxSDL (simple frontend based on SDL): * VBoxSDL testcases */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4121) #endif #if defined(RT_OS_WINDOWS) /// @todo someone please explain why we don't follow the book! # define _SDL_main_h #endif #include #ifdef _MSC_VER # pragma warning(pop) #endif #include #include #include #include #include #include #include #include #ifdef VBOX_OPENGL #include "SDL_opengl.h" #endif #ifdef RT_OS_WINDOWS #define ESC_NORM #define ESC_BOLD #else #define ESC_NORM "\033[m" #define ESC_BOLD "\033[1m" #endif static SDL_Surface *gSurfVRAM; /* SDL virtual framebuffer surface */ static void *gPtrVRAM; /* allocated virtual framebuffer */ static SDL_Surface *gScreen; /* SDL screen surface */ static unsigned long guGuestXRes; /* virtual framebuffer width */ static unsigned long guGuestYRes; /* virtual framebuffer height */ static unsigned long guGuestBpp; /* virtual framebuffer bits per pixel */ static unsigned long guMaxScreenWidth; /* max screen width SDL allows */ static unsigned long guMaxScreenHeight; /* max screen height SDL allows */ static int gfResizable = 1; /* SDL window is resizable */ static int gfFullscreen = 0; /* use fullscreen mode */ #ifdef VBOX_OPENGL static unsigned long guTextureWidth; /* width of OpenGL texture */ static unsigned long guTextureHeight; /* height of OpenGL texture */ static unsigned int gTexture; static int gfOpenGL; /* use OpenGL as backend */ #endif static unsigned int guLoop = 1000; /* Number of frame redrawings for each test */ static void bench(unsigned long w, unsigned long h, unsigned long bpp); static void benchExecute(void); static int checkSDL(const char *fn, int rc); static void checkEvents(void); int main(int argc, char **argv) { int rc; RTR3InitExe(argc, &argv, 0); for (int i = 1; i < argc; i++) { #ifdef VBOX_OPENGL if (strcmp(argv[i], "-gl") == 0) { gfOpenGL = 1; continue; } #endif if (strcmp(argv[i], "-loop") == 0 && ++i < argc) { guLoop = atoi(argv[i]); continue; } RTPrintf("Unrecognized option '%s'\n", argv[i]); return -1; } #ifdef RT_OS_WINDOWS /* Default to DirectX if nothing else set. "windib" would be possible. */ if (!RTEnvExist("SDL_VIDEODRIVER")) { _putenv("SDL_VIDEODRIVER=directx"); } #endif #ifdef RT_OS_WINDOWS _putenv("SDL_VIDEO_WINDOW_POS=0,0"); #else RTEnvSet("SDL_VIDEO_WINDOW_POS", "0,0"); #endif rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE); if (rc != 0) { RTPrintf("Error: SDL_InitSubSystem failed with message '%s'\n", SDL_GetError()); return -1; } /* output what SDL is capable of */ const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); if (!videoInfo) { RTPrintf("No SDL video info available!\n"); return -1; } RTPrintf("SDL capabilities:\n"); RTPrintf(" Hardware surface support: %s\n", videoInfo->hw_available ? "yes" : "no"); RTPrintf(" Window manager available: %s\n", videoInfo->wm_available ? "yes" : "no"); RTPrintf(" Screen to screen blits accelerated: %s\n", videoInfo->blit_hw ? "yes" : "no"); RTPrintf(" Screen to screen colorkey blits accelerated: %s\n", videoInfo->blit_hw_CC ? "yes" : "no"); RTPrintf(" Screen to screen alpha blits accelerated: %s\n", videoInfo->blit_hw_A ? "yes" : "no"); RTPrintf(" Memory to screen blits accelerated: %s\n", videoInfo->blit_sw ? "yes" : "no"); RTPrintf(" Memory to screen colorkey blits accelerated: %s\n", videoInfo->blit_sw_CC ? "yes" : "no"); RTPrintf(" Memory to screen alpha blits accelerated: %s\n", videoInfo->blit_sw_A ? "yes" : "no"); RTPrintf(" Color fills accelerated: %s\n", videoInfo->blit_fill ? "yes" : "no"); RTPrintf(" Video memory in kilobytes: %d\n", videoInfo->video_mem); RTPrintf(" Optimal bpp mode: %d\n", videoInfo->vfmt->BitsPerPixel); char buf[256]; RTPrintf("Video driver SDL_VIDEODRIVER / active: %s/%s\n", RTEnvGet("SDL_VIDEODRIVER"), SDL_VideoDriverName(buf, sizeof(buf))); RTPrintf("\n" "Starting tests. Any key pressed inside the SDL window will abort this\n" "program at the end of the current test. Iterations = %u\n", guLoop); #ifdef VBOX_OPENGL RTPrintf("\n========== "ESC_BOLD"OpenGL is %s"ESC_NORM" ==========\n", gfOpenGL ? "ON" : "OFF"); #endif bench( 640, 480, 16); bench( 640, 480, 24); bench( 640, 480, 32); bench(1024, 768, 16); bench(1024, 768, 24); bench(1024, 768, 32); bench(1280, 1024, 16); bench(1280, 1024, 24); bench(1280, 1024, 32); RTPrintf("\nSuccess!\n"); return 0; } /** * Method that does the actual resize of the guest framebuffer and * then changes the SDL framebuffer setup. */ static void bench(unsigned long w, unsigned long h, unsigned long bpp) { Uint32 Rmask, Gmask, Bmask, Amask = 0; Uint32 Rsize, Gsize, Bsize; Uint32 newWidth, newHeight; guGuestXRes = w; guGuestYRes = h; guGuestBpp = bpp; RTPrintf("\n"); /* a different format we support directly? */ switch (guGuestBpp) { case 16: { Rmask = 0xF800; Gmask = 0x07E0; Bmask = 0x001F; Amask = 0x0000; Rsize = 5; Gsize = 6; Bsize = 5; break; } case 24: { Rmask = 0x00FF0000; Gmask = 0x0000FF00; Bmask = 0x000000FF; Amask = 0x00000000; Rsize = 8; Gsize = 8; Bsize = 8; break; } default: Rmask = 0x00FF0000; Gmask = 0x0000FF00; Bmask = 0x000000FF; Amask = 0x00000000; Rsize = 8; Gsize = 8; Bsize = 8; break; } int sdlFlags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; #ifdef VBOX_OPENGL if (gfOpenGL) sdlFlags |= SDL_OPENGL; #endif if (gfResizable) sdlFlags |= SDL_RESIZABLE; if (gfFullscreen) sdlFlags |= SDL_FULLSCREEN; /* * Now we have to check whether there are video mode restrictions */ SDL_Rect **modes; /* Get available fullscreen/hardware modes */ modes = SDL_ListModes(NULL, sdlFlags); if (modes == NULL) { RTPrintf("Error: SDL_ListModes failed with message '%s'\n", SDL_GetError()); return; } /* -1 means that any mode is possible (usually non fullscreen) */ if (modes != (SDL_Rect **)-1) { /* * according to the SDL documentation, the API guarantees that * the modes are sorted from larger to smaller, so we just * take the first entry as the maximum. */ guMaxScreenWidth = modes[0]->w; guMaxScreenHeight = modes[0]->h; } else { /* no restriction */ guMaxScreenWidth = ~0U; guMaxScreenHeight = ~0U; } newWidth = RT_MIN(guMaxScreenWidth, guGuestXRes); newHeight = RT_MIN(guMaxScreenHeight, guGuestYRes); /* * Now set the screen resolution and get the surface pointer * @todo BPP is not supported! */ #ifdef VBOX_OPENGL if (gfOpenGL) { checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_RED_SIZE, Rsize)); checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, Gsize)); checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, Bsize)); checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0)); } #else NOREF(Rsize); NOREF(Gsize); NOREF(Bsize); #endif RTPrintf("Testing " ESC_BOLD "%ldx%ld@%ld" ESC_NORM "\n", guGuestXRes, guGuestYRes, guGuestBpp); gScreen = SDL_SetVideoMode(newWidth, newHeight, 0, sdlFlags); if (!gScreen) { RTPrintf("SDL_SetVideoMode failed (%s)\n", SDL_GetError()); return; } /* first free the current surface */ if (gSurfVRAM) { SDL_FreeSurface(gSurfVRAM); gSurfVRAM = NULL; } if (gPtrVRAM) { free(gPtrVRAM); gPtrVRAM = NULL; } if (gScreen->format->BitsPerPixel != guGuestBpp) { /* Create a source surface from guest VRAM. */ int bytes_per_pixel = (guGuestBpp + 7) / 8; gPtrVRAM = malloc(guGuestXRes * guGuestYRes * bytes_per_pixel); gSurfVRAM = SDL_CreateRGBSurfaceFrom(gPtrVRAM, guGuestXRes, guGuestYRes, guGuestBpp, bytes_per_pixel * guGuestXRes, Rmask, Gmask, Bmask, Amask); } else { /* Create a software surface for which SDL allocates the RAM */ gSurfVRAM = SDL_CreateRGBSurface(SDL_SWSURFACE, guGuestXRes, guGuestYRes, guGuestBpp, Rmask, Gmask, Bmask, Amask); } if (!gSurfVRAM) { RTPrintf("Failed to allocate surface %ldx%ld@%ld\n", guGuestXRes, guGuestYRes, guGuestBpp); return; } RTPrintf(" gScreen=%dx%d@%d (surface: %s)\n", gScreen->w, gScreen->h, gScreen->format->BitsPerPixel, (gScreen->flags & SDL_HWSURFACE) == 0 ? "software" : "hardware"); SDL_Rect rect = { 0, 0, (Uint16)guGuestXRes, (Uint16)guGuestYRes }; checkSDL("SDL_FillRect", SDL_FillRect(gSurfVRAM, &rect, SDL_MapRGB(gSurfVRAM->format, 0x5F, 0x6F, 0x1F))); #ifdef VBOX_OPENGL if (gfOpenGL) { int r, g, b, d, o; SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r); SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g); SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b); SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &d); SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &o); RTPrintf(" OpenGL ctxt red=%d, green=%d, blue=%d, depth=%d, dbl=%d", r, g, b, d, o); glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); glGenTextures(1, &gTexture); glBindTexture(GL_TEXTURE_2D, gTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); for (guTextureWidth = 32; guTextureWidth < newWidth; guTextureWidth <<= 1) ; for (guTextureHeight = 32; guTextureHeight < newHeight; guTextureHeight <<= 1) ; RTPrintf(", tex %ldx%ld\n", guTextureWidth, guTextureHeight); switch (guGuestBpp) { case 16: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, guTextureWidth, guTextureHeight, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); break; case 24: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, guTextureWidth, guTextureHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, 0); break; case 32: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, guTextureWidth, guTextureHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); break; default: RTPrintf("guGuestBpp=%d?\n", guGuestBpp); return; } glViewport(0, 0, newWidth, newHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, newWidth, newHeight, 0.0, -1.0, 1.0); } #endif checkEvents(); benchExecute(); #ifdef VBOX_OPENGL if (gfOpenGL) { glDeleteTextures(1, &gTexture); } #endif } static void benchExecute() { SDL_Rect rect = { 0, 0, (Uint16)guGuestXRes, (Uint16)guGuestYRes }; RTTIMESPEC t1, t2; RTTimeNow(&t1); for (unsigned i=0; iflags & SDL_HWSURFACE) == 0) SDL_UpdateRect(gScreen, rect.x, rect.y, rect.w, rect.h); #ifdef VBOX_OPENGL } else { /* OpenGL backend */ glBindTexture(GL_TEXTURE_2D, gTexture); glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x); glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y); glPixelStorei(GL_UNPACK_ROW_LENGTH, gSurfVRAM->pitch / gSurfVRAM->format->BytesPerPixel); switch (gSurfVRAM->format->BitsPerPixel) { case 16: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, gSurfVRAM->pixels); break; case 24: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, GL_BGR, GL_UNSIGNED_BYTE, gSurfVRAM->pixels); break; case 32: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, GL_BGRA, GL_UNSIGNED_BYTE, gSurfVRAM->pixels); break; default: RTPrintf("BitsPerPixel=%d?\n", gSurfVRAM->format->BitsPerPixel); return; } GLfloat tx = (GLfloat)((float)rect.w) / guTextureWidth; GLfloat ty = (GLfloat)((float)rect.h) / guTextureHeight; glBegin(GL_QUADS); glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0.0, 0.0); glVertex2i(rect.x, rect.y ); glTexCoord2f(0.0, ty); glVertex2i(rect.x, rect.y + rect.h); glTexCoord2f(tx, ty); glVertex2i(rect.x + rect.w, rect.y + rect.h); glTexCoord2f(tx, 0.0); glVertex2i(rect.x + rect.w, rect.y ); glEnd(); glFlush(); } #endif } RTTimeNow(&t2); int64_t ms = RTTimeSpecGetMilli(&t2) - RTTimeSpecGetMilli(&t1); printf(" %.1fms/frame\n", (double)ms / guLoop); } static int checkSDL(const char *fn, int rc) { if (rc == -1) RTPrintf("" ESC_BOLD "%s() failed:" ESC_NORM " '%s'\n", fn, SDL_GetError()); return rc; } static void checkEvents(void) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: RTPrintf("\nKey pressed, exiting ...\n"); exit(-1); break; } } }