openMSX
SDLGLVisibleSurface.cc
Go to the documentation of this file.
3#include "GLContext.hh"
4#include "GLSnow.hh"
6#include "OSDGUILayer.hh"
7#include "Display.hh"
8#include "RenderSettings.hh"
9#include "PNG.hh"
10#include "MemBuffer.hh"
11#include "outer.hh"
12#include "vla.hh"
13#include "InitException.hh"
14#include <memory>
15
16#include "GLUtil.hh"
17
18namespace openmsx {
19
21 int width, int height,
22 Display& display_,
23 RTScheduler& rtScheduler_,
24 EventDistributor& eventDistributor_,
25 InputEventGenerator& inputEventGenerator_,
26 CliComm& cliComm_,
27 VideoSystem& videoSystem_)
28 : SDLVisibleSurfaceBase(display_, rtScheduler_, eventDistributor_,
29 inputEventGenerator_, cliComm_, videoSystem_)
30{
31 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
32 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
33 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
34#if OPENGL_VERSION == OPENGL_ES_2_0
35 #define VERSION_STRING "openGL ES 2.0"
36 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
37 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
38 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
39#elif OPENGL_VERSION == OPENGL_2_1
40 #define VERSION_STRING "openGL 2.1"
41 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
42 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
43 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
44#elif OPENGL_VERSION == OPENGL_3_3
45 #define VERSION_STRING "openGL 3.3"
46 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
47 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
48 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
49#endif
50
51 int flags = SDL_WINDOW_OPENGL;
52 //flags |= SDL_RESIZABLE;
53 createSurface(width, height, flags);
54
55 glContext = SDL_GL_CreateContext(window.get());
56 if (!glContext) {
57 throw InitException(
58 "Failed to create " VERSION_STRING " context: ", SDL_GetError());
59 }
60
61 // From the glew documentation:
62 // GLEW obtains information on the supported extensions from the
63 // graphics driver. Experimental or pre-release drivers, however,
64 // might not report every available extension through the standard
65 // mechanism, in which case GLEW will report it unsupported. To
66 // circumvent this situation, the glewExperimental global switch can
67 // be turned on by setting it to GL_TRUE before calling glewInit(),
68 // which ensures that all extensions with valid entry points will be
69 // exposed.
70 // The 'glewinfo' utility also sets this flag before reporting results,
71 // so I believe it would cause less confusion to do the same here.
72 glewExperimental = GL_TRUE;
73
74 // Initialise GLEW library.
75 GLenum glew_error = glewInit();
76
77 // GLEW fails to initialise on Wayland because it has no GLX, since the
78 // one provided by the distros was built to use GLX instead of EGL. We
79 // ignore the GLEW_ERROR_NO_GLX_DISPLAY error with this temporary fix
80 // until it is fixed by GLEW upstream and released by major distros.
81 // See https://github.com/nigels-com/glew/issues/172
82 if (glew_error != GLEW_OK && glew_error != GLEW_ERROR_NO_GLX_DISPLAY) {
83 throw InitException(
84 "Failed to init GLEW: ",
85 reinterpret_cast<const char*>(
86 glewGetErrorString(glew_error)));
87 }
88 if (!GLEW_VERSION_2_1) {
89 throw InitException(
90 "Need at least OpenGL version " VERSION_STRING);
91 }
92
93 bool fullScreen = getDisplay().getRenderSettings().getFullScreen();
94 setViewPort(gl::ivec2(width, height), fullScreen); // set initial values
95
97 gl::context.emplace(width, height);
98
100 // set initial value
101 vSyncObserver.update(getDisplay().getRenderSettings().getVSyncSetting());
102
103#if OPENGL_VERSION == OPENGL_3_3
104 // We don't (yet/anymore) use VAO, but apparently it's required in openGL 3.3.
105 // Luckily this workaround is sufficient: create one global VAO and then don't care anymore.
106 GLuint vao;
107 glGenVertexArrays(1, &vao);
108 glBindVertexArray(vao);
109#endif
110}
111
113{
115
116 gl::context.reset();
117 SDL_GL_DeleteContext(glContext);
118}
119
120void SDLGLVisibleSurface::saveScreenshot(const std::string& filename)
121{
122 saveScreenshotGL(*this, filename);
123}
124
126 const OutputSurface& output, const std::string& filename)
127{
128 auto [x, y] = output.getViewOffset();
129 auto [w, h] = output.getViewSize();
130
131 // OpenGL ES only supports reading RGBA (not RGB)
132 MemBuffer<uint8_t> buffer(4 * size_t(w) * size_t(h));
133 glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
134
135 // perform in-place conversion of RGBA -> RGB
136 VLA(const void*, rowPointers, h);
137 for (auto i : xrange(size_t(h))) {
138 uint8_t* out = &buffer[4 * size_t(w) * i];
139 const uint8_t* in = out;
140 rowPointers[h - 1 - i] = out;
141
142 for (auto j : xrange(size_t(w))) {
143 out[3 * j + 0] = in[4 * j + 0];
144 out[3 * j + 1] = in[4 * j + 1];
145 out[3 * j + 2] = in[4 * j + 2];
146 }
147 }
148
149 PNG::save(w, rowPointers, filename);
150}
151
153{
154 SDL_GL_SwapWindow(window.get());
155}
156
158{
159 return std::make_unique<GLSnow>(getDisplay());
160}
161
163 Reactor& reactor, CommandConsole& console)
164{
165 const bool openGL = true;
166 auto [width, height] = getLogicalSize();
167 return std::make_unique<OSDConsoleRenderer>(
168 reactor, console, width, height, openGL);
169}
170
172{
173 return std::make_unique<GLOSDGUILayer>(gui);
174}
175
176std::unique_ptr<OutputSurface> SDLGLVisibleSurface::createOffScreenSurface()
177{
178 return std::make_unique<SDLGLOffScreenSurface>(*this);
179}
180
181void SDLGLVisibleSurface::VSyncObserver::update(const Setting& setting) noexcept
182{
183 auto& visSurface = OUTER(SDLGLVisibleSurface, vSyncObserver);
184 auto& syncSetting = visSurface.getDisplay().getRenderSettings().getVSyncSetting();
185 assert(&setting == &syncSetting); (void)setting;
186
187 // for now, we assume that adaptive vsync is the best kind of vsync, so when
188 // vsync is enabled, we attempt adaptive vsync.
189 int interval = syncSetting.getBoolean() ? -1 : 0;
190
191 if ((SDL_GL_SetSwapInterval(interval) < 0) && (interval == -1)) {
192 // "Adaptive vsync" is not supported by all drivers. SDL
193 // documentation suggests to fallback to "regular vsync" in
194 // that case.
195 SDL_GL_SetSwapInterval(1);
196 }
197}
198
199void SDLGLVisibleSurface::setViewPort(gl::ivec2 logicalSize, bool fullScreen)
200{
201 gl::ivec2 physicalSize = [&] {
202#ifndef __APPLE__
203 // On macos we set 'SDL_WINDOW_ALLOW_HIGHDPI', and in that case
204 // it's required to use SDL_GL_GetDrawableSize(), but then this
205 // 'full screen'-workaround/hack is counter-productive.
206 if (!fullScreen) {
207 // ??? When switching back from full screen to windowed mode,
208 // SDL_GL_GetDrawableSize() still returns the dimensions of the
209 // full screen window ??? Is this a bug ???
210 // But we know that in windowed mode, physical and logical size
211 // must be the same, so enforce that.
212 return logicalSize;
213 }
214#endif
215 (void)fullScreen;
216 int w, h;
217 SDL_GL_GetDrawableSize(window.get(), &w, &h);
218 return gl::ivec2(w, h);
219 }();
220
221 // The created surface may be larger than requested.
222 // If that happens, center the area that we actually use.
223 calculateViewPort(logicalSize, physicalSize);
224 auto [vx, vy] = getViewOffset();
225 auto [vw, vh] = getViewSize();
226 glViewport(vx, vy, vw, vh);
227}
228
230{
231 setViewPort(getLogicalSize(), fullScreen);
232}
233
234} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
#define VERSION_STRING
Represents the output window/screen of openMSX.
Definition: Display.hh:33
RenderSettings & getRenderSettings()
Definition: Display.hh:44
Thrown when a subsystem initialisation fails.
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
A frame buffer where pixels can be written to.
gl::ivec2 getLogicalSize() const
gl::ivec2 getViewOffset() const
void calculateViewPort(gl::ivec2 logSize, gl::ivec2 physSize)
Definition: OutputSurface.cc:6
gl::ivec2 getViewSize() const
Contains the main loop of openMSX.
Definition: Reactor.hh:68
BooleanSetting & getVSyncSetting()
VSync [on, off] ATM this only works when using the SDLGL-PP renderer.
Visible surface for SDL openGL renderers.
void fullScreenUpdated(bool fullScreen) override
void saveScreenshot(const std::string &filename) override
Save the content of this OutputSurface to a PNG file.
std::unique_ptr< Layer > createSnowLayer() override
std::unique_ptr< OutputSurface > createOffScreenSurface() override
Create an off-screen OutputSurface which has similar properties as this VisibleSurface.
std::unique_ptr< Layer > createConsoleLayer(Reactor &reactor, CommandConsole &console) override
std::unique_ptr< Layer > createOSDGUILayer(OSDGUI &gui) override
void finish() override
When a complete frame is finished, call this method.
static void saveScreenshotGL(const OutputSurface &output, const std::string &filename)
SDLGLVisibleSurface(int width, int height, Display &display, RTScheduler &rtScheduler, EventDistributor &eventDistributor, InputEventGenerator &inputEventGenerator, CliComm &cliComm, VideoSystem &videoSystem)
Common functionality for the plain SDL and SDLGL VisibleSurface classes.
void createSurface(int width, int height, unsigned flags)
void detach(Observer< T > &observer)
Definition: Subject.hh:56
void attach(Observer< T > &observer)
Definition: Subject.hh:50
Video back-end system.
Definition: VideoSystem.hh:23
Display & getDisplay() const
vecN< 2, int > ivec2
Definition: gl_vec.hh:153
std::optional< Context > context
Definition: GLContext.cc:10
This file implemented 3 utility functions:
Definition: Autofire.cc:9
#define OUTER(type, member)
Definition: outer.hh:41
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:12
constexpr auto xrange(T e)
Definition: xrange.hh:132