Integrate Dear ImGui with SDL to reduce code duplication.
Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
parent
05b2d5b2e7
commit
989410c17d
12 changed files with 287 additions and 14 deletions
|
@ -24,6 +24,7 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & edit
|
|||
("multi-threaded,m", "Multi threaded")
|
||||
("loose-mutex,l", "Loose mutex")
|
||||
("sdl-driver", po::value<int>()->default_value(options.sdlDriver), "SDL driver")
|
||||
("imgui", "Render with Dear ImGui")
|
||||
("timer-interval,i", po::value<int>()->default_value(options.timerInterval), "Timer interval in ms");
|
||||
|
||||
po::options_description configDesc("configuration");
|
||||
|
@ -82,6 +83,7 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & edit
|
|||
options.looseMutex = vm.count("loose-mutex");
|
||||
options.timerInterval = vm["timer-interval"].as<int>();
|
||||
options.sdlDriver = vm["sdl-driver"].as<int>();
|
||||
options.imgui = vm.count("imgui");
|
||||
|
||||
if (vm.count("config"))
|
||||
{
|
||||
|
|
|
@ -36,6 +36,7 @@ struct EmulatorOptions
|
|||
bool fixedSpeed = false; // default adaptive
|
||||
|
||||
int sdlDriver = -1; // default = -1 to let SDL choose
|
||||
bool imgui = false; // use imgui renderer
|
||||
|
||||
std::vector<std::string> registryOptions;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
include(FindPkgConfig)
|
||||
|
||||
add_executable(sa2)
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
emulator.cpp
|
||||
|
@ -7,7 +9,7 @@ set(SOURCE_FILES
|
|||
sdirectsound.cpp
|
||||
utils.cpp
|
||||
sdlframe.cpp
|
||||
sdlrendererframe.cpp
|
||||
renderer/sdlrendererframe.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
|
@ -16,12 +18,7 @@ set(HEADER_FILES
|
|||
sdirectsound.h
|
||||
utils.h
|
||||
sdlframe.h
|
||||
sdlrendererframe.h
|
||||
)
|
||||
|
||||
add_executable(sa2
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
renderer/sdlrendererframe.h
|
||||
)
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
@ -41,5 +38,50 @@ target_link_libraries(sa2 PRIVATE
|
|||
common2
|
||||
)
|
||||
|
||||
target_sources(sa2 PRIVATE
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
set(IMGUI_PATH NONE CACHE PATH "path to imgui")
|
||||
|
||||
if (EXISTS ${IMGUI_PATH}/imgui.h)
|
||||
message("Using IMGUI_PATH=${IMGUI_PATH}")
|
||||
|
||||
pkg_search_module(GLES2 REQUIRED glesv2)
|
||||
|
||||
target_sources(sa2 PRIVATE
|
||||
imgui/sdlimguiframe.cpp
|
||||
imgui/image.cpp
|
||||
imgui/gles.h
|
||||
imgui/image.h
|
||||
imgui/sdlimguiframe.h
|
||||
${IMGUI_PATH}/imgui.cpp
|
||||
${IMGUI_PATH}/imgui_demo.cpp
|
||||
${IMGUI_PATH}/imgui_draw.cpp
|
||||
${IMGUI_PATH}/imgui_tables.cpp
|
||||
${IMGUI_PATH}/imgui_widgets.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_sdl.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
target_include_directories(sa2 PRIVATE
|
||||
${IMGUI_PATH}
|
||||
${IMGUI_PATH}/backends
|
||||
)
|
||||
|
||||
target_link_libraries(sa2 PRIVATE
|
||||
${GLES2_LIBRARIES}
|
||||
)
|
||||
|
||||
target_compile_definitions(sa2 PRIVATE
|
||||
SA2_IMGUI
|
||||
IMGUI_IMPL_OPENGL_ES2
|
||||
)
|
||||
else()
|
||||
message("Bad IMGUI_PATH=${IMGUI_PATH}, skipping imgui code")
|
||||
endif()
|
||||
|
||||
|
||||
install(TARGETS sa2
|
||||
DESTINATION bin)
|
||||
|
|
33
source/frontends/sdl/imgui/gles.h
Normal file
33
source/frontends/sdl/imgui/gles.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
|
||||
// Pi3 with Fake KMS
|
||||
// "OpenGL ES 2.0 Mesa 19.3.2"
|
||||
// "Supported versions are: 1.00 ES"
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#define SDL_CONTEXT_MAJOR 2
|
||||
|
||||
#elif defined(IMGUI_IMPL_OPENGL_ES3)
|
||||
|
||||
// Pi4 with Fake KMS
|
||||
// "OpenGL ES 3.1 Mesa 19.3.2"
|
||||
// "Supported versions are: 1.00 ES, 3.00 ES, and 3.10 ES"
|
||||
|
||||
// On my desktop
|
||||
// "OpenGL ES 3.1 Mesa 20.2.6"
|
||||
// "Supported versions are: 1.00 ES, 3.00 ES, and 3.10 ES"
|
||||
|
||||
#include <GLES3/gl3.h>
|
||||
#define SDL_CONTEXT_MAJOR 3
|
||||
// "310 es" is accepted on a Pi4, but the imgui shaders do not compile
|
||||
|
||||
#endif
|
||||
|
||||
// this is used in all cases for GL_BGRA_EXT
|
||||
#include <GLES2/gl2ext.h>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl.h"
|
||||
#include "imgui_impl_opengl3.h"
|
43
source/frontends/sdl/imgui/image.cpp
Normal file
43
source/frontends/sdl/imgui/image.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "frontends/sdl/imgui/image.h"
|
||||
|
||||
#ifdef GL_UNPACK_ROW_LENGTH
|
||||
// GLES3: defined in gl3.h
|
||||
#define UGL_UNPACK_LENGTH GL_UNPACK_ROW_LENGTH
|
||||
#else
|
||||
// GLES2: defined in gl2ext.h as an extension GL_EXT_unpack_subimage
|
||||
#define UGL_UNPACK_LENGTH GL_UNPACK_ROW_LENGTH_EXT
|
||||
#endif
|
||||
|
||||
/*
|
||||
2 extensions are used here
|
||||
|
||||
ES2: GL_UNPACK_ROW_LENGTH_EXT and GL_BGRA_EXT
|
||||
ES3: GL_BGRA_EXT
|
||||
|
||||
They both work on Pi3/4 and my Intel Haswell card.
|
||||
|
||||
What does SDL do about it?
|
||||
|
||||
GL_UNPACK_ROW_LENGTH_EXT: it uses instead texture coordinates
|
||||
GL_BGRA_EXT: it uses a shader to swap the color bytes
|
||||
|
||||
For simplicity (and because it just works) here we are using these 2 extensions.
|
||||
*/
|
||||
|
||||
void LoadTextureFromData(GLuint texture, const uint8_t * data, size_t width, size_t height, size_t pitch)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glPixelStorei(UGL_UNPACK_LENGTH, pitch); // in pixels
|
||||
|
||||
// Setup filtering parameters for display
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
const GLenum format = GL_BGRA_EXT; // this is defined in gl2ext.h and nowhere in gl3.h
|
||||
const GLenum type = GL_UNSIGNED_BYTE;
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
|
||||
// reset to default state
|
||||
glPixelStorei(UGL_UNPACK_LENGTH, 0);
|
||||
}
|
6
source/frontends/sdl/imgui/image.h
Normal file
6
source/frontends/sdl/imgui/image.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "frontends/sdl/imgui/gles.h"
|
||||
|
||||
void LoadTextureFromData(GLuint texture, const uint8_t * data, size_t width, size_t height, size_t pitch);
|
104
source/frontends/sdl/imgui/sdlimguiframe.cpp
Normal file
104
source/frontends/sdl/imgui/sdlimguiframe.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/sdl/imgui/sdlimguiframe.h"
|
||||
#include "frontends/sdl/imgui/image.h"
|
||||
#include "frontends/sdl/utils.h"
|
||||
|
||||
#include "Interface.h"
|
||||
#include "Core.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
SDLImGuiFrame::SDLImGuiFrame()
|
||||
{
|
||||
Video & video = GetVideo();
|
||||
|
||||
myBorderlessWidth = video.GetFrameBufferBorderlessWidth();
|
||||
myBorderlessHeight = video.GetFrameBufferBorderlessHeight();
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, SDL_CONTEXT_MAJOR); // from local gles.h
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
|
||||
// Create window with graphics context
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
SDL_WindowFlags windowFlags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
myWindow.reset(SDL_CreateWindow(g_pAppTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, myBorderlessWidth, myBorderlessHeight, windowFlags), SDL_DestroyWindow);
|
||||
if (!myWindow)
|
||||
{
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
myGLContext = SDL_GL_CreateContext(myWindow.get());
|
||||
if (!myGLContext)
|
||||
{
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_GL_MakeCurrent(myWindow.get(), myGLContext);
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
std::cerr << "GL_VENDOR: " << glGetString(GL_VENDOR) << std::endl;
|
||||
std::cerr << "GL_RENDERER: " << glGetString(GL_RENDERER) << std::endl;
|
||||
std::cerr << "GL_VERSION: " << glGetString(GL_VERSION) << std::endl;
|
||||
std::cerr << "GL_SHADING_LANGUAGE_VERSION: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
|
||||
// const char* runtime_gl_extensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||
// std::cerr << "GL_EXTENSIONS: " << runtime_gl_extensions << std::endl;
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
ImGui_ImplSDL2_InitForOpenGL(myWindow.get(), myGLContext);
|
||||
|
||||
ImGui_ImplOpenGL3_Init();
|
||||
|
||||
glGenTextures(1, &myTexture);
|
||||
|
||||
const int width = video.GetFrameBufferWidth();
|
||||
const size_t borderWidth = video.GetFrameBufferBorderWidth();
|
||||
const size_t borderHeight = video.GetFrameBufferBorderHeight();
|
||||
|
||||
myPitch = width;
|
||||
myOffset = (width * borderHeight + borderWidth) * sizeof(bgra_t);
|
||||
}
|
||||
|
||||
SDLImGuiFrame::~SDLImGuiFrame()
|
||||
{
|
||||
glDeleteTextures(1, &myTexture);
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_GL_DeleteContext(myGLContext);
|
||||
}
|
||||
|
||||
void SDLImGuiFrame::UpdateTexture()
|
||||
{
|
||||
LoadTextureFromData(myTexture, myFramebuffer.data() + myOffset, myBorderlessWidth, myBorderlessHeight, myPitch);
|
||||
}
|
||||
|
||||
void SDLImGuiFrame::RenderPresent()
|
||||
{
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(myWindow.get());
|
||||
ImGui::NewFrame();
|
||||
|
||||
// need to flip the texture vertically
|
||||
const ImVec2 uv0(0, 1);
|
||||
const ImVec2 uv1(1, 0);
|
||||
const ImVec2 zero(0, 0);
|
||||
// draw on the background
|
||||
ImGui::GetBackgroundDrawList()->AddImage((void*)(intptr_t)myTexture, zero, ImGui::GetIO().DisplaySize, uv0, uv1);
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_GL_SwapWindow(myWindow.get());
|
||||
}
|
24
source/frontends/sdl/imgui/sdlimguiframe.h
Normal file
24
source/frontends/sdl/imgui/sdlimguiframe.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/sdl/sdlframe.h"
|
||||
#include "frontends/sdl/imgui/gles.h"
|
||||
|
||||
class SDLImGuiFrame : public SDLFrame
|
||||
{
|
||||
public:
|
||||
SDLImGuiFrame();
|
||||
|
||||
~SDLImGuiFrame() override;
|
||||
|
||||
void UpdateTexture() override;
|
||||
void RenderPresent() override;
|
||||
|
||||
private:
|
||||
size_t myPitch;
|
||||
size_t myOffset;
|
||||
size_t myBorderlessWidth;
|
||||
size_t myBorderlessHeight;
|
||||
|
||||
SDL_GLContext myGLContext;
|
||||
GLuint myTexture;
|
||||
};
|
|
@ -17,7 +17,12 @@
|
|||
#include "frontends/sdl/gamepad.h"
|
||||
#include "frontends/sdl/sdirectsound.h"
|
||||
#include "frontends/sdl/utils.h"
|
||||
#include "frontends/sdl/sdlrendererframe.h"
|
||||
|
||||
#include "frontends/sdl/renderer/sdlrendererframe.h"
|
||||
|
||||
#ifdef SA2_IMGUI
|
||||
#include "frontends/sdl/imgui/sdlimguiframe.h"
|
||||
#endif
|
||||
|
||||
#include "Core.h"
|
||||
#include "Log.h"
|
||||
|
@ -79,7 +84,21 @@ void run_sdl(int argc, const char * argv [])
|
|||
if (!run)
|
||||
return;
|
||||
|
||||
const std::shared_ptr<SDLFrame> frame(new SDLRendererFrame(options));
|
||||
std::shared_ptr<SDLFrame> frame;
|
||||
|
||||
#ifdef SA2_IMGUI
|
||||
if (options.imgui)
|
||||
{
|
||||
frame.reset(new SDLImGuiFrame());
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.reset(new SDLRendererFrame(options));
|
||||
}
|
||||
#else
|
||||
frame.reset(new SDLRendererFrame(options));
|
||||
#endif
|
||||
|
||||
SetFrame(frame);
|
||||
|
||||
if (options.log)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/sdl/sdlrendererframe.h"
|
||||
#include "frontends/sdl/renderer/sdlrendererframe.h"
|
||||
#include "frontends/sdl/utils.h"
|
||||
#include "frontends/common2/programoptions.h"
|
||||
|
||||
|
@ -20,8 +20,7 @@ SDLRendererFrame::SDLRendererFrame(const EmulatorOptions & options)
|
|||
myWindow.reset(SDL_CreateWindow(g_pAppTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, sw, sh, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE), SDL_DestroyWindow);
|
||||
if (!myWindow)
|
||||
{
|
||||
std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
|
||||
return;
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
SetApplicationIcon();
|
||||
|
@ -29,8 +28,7 @@ SDLRendererFrame::SDLRendererFrame(const EmulatorOptions & options)
|
|||
myRenderer.reset(SDL_CreateRenderer(myWindow.get(), options.sdlDriver, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), SDL_DestroyRenderer);
|
||||
if (!myRenderer)
|
||||
{
|
||||
std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
|
||||
return;
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
const Uint32 format = SDL_PIXELFORMAT_ARGB8888;
|
|
@ -10,3 +10,4 @@ libncurses-dev
|
|||
libevdev-dev
|
||||
libsdl2-image-dev
|
||||
libsdl2-dev
|
||||
libgles-dev
|
||||
|
|
Loading…
Add table
Reference in a new issue