openMSX
SDLGLVisibleSurface.cc
Go to the documentation of this file.
1 #include "SDLGLVisibleSurface.hh"
3 #include "GLContext.hh"
4 #include "GLSnow.hh"
5 #include "OSDConsoleRenderer.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 
18 namespace openmsx {
19 
21  unsigned width, unsigned 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  if (glew_error != GLEW_OK) {
77  throw InitException(
78  "Failed to init GLEW: ",
79  reinterpret_cast<const char*>(
80  glewGetErrorString(glew_error)));
81  }
82 
83  bool fullscreen = getDisplay().getRenderSettings().getFullScreen();
84  setViewPort(gl::ivec2(width, height), fullscreen); // set initial values
85 
87  gl::context.emplace(width, height);
88 
90  // set initial value
91  vSyncObserver.update(getDisplay().getRenderSettings().getVSyncSetting());
92 
93 #if OPENGL_VERSION == OPENGL_3_3
94  // We don't (yet/anymore) use VAO, but apparently it's required in openGL 3.3.
95  // Luckily this workaround is sufficient: create one global VAO and then don't care anymore.
96  GLuint vao;
97  glGenVertexArrays(1, &vao);
98  glBindVertexArray(vao);
99 #endif
100 }
101 
103 {
104  getDisplay().getRenderSettings().getVSyncSetting().detach(vSyncObserver);
105 
106  gl::context.reset();
107  SDL_GL_DeleteContext(glContext);
108 }
109 
111 {
112  saveScreenshotGL(*this, filename);
113 }
114 
116  const OutputSurface& output, const std::string& filename)
117 {
118  auto [x, y] = output.getViewOffset();
119  auto [w, h] = output.getViewSize();
120 
121  // OpenGL ES only supports reading RGBA (not RGB)
122  MemBuffer<uint8_t> buffer(w * h * 4);
123  glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
124 
125  // perform in-place conversion of RGBA -> RGB
126  VLA(const void*, rowPointers, h);
127  for (auto i : xrange(h)) {
128  uint8_t* out = &buffer[w * 4 * i];
129  const uint8_t* in = out;
130  rowPointers[h - 1 - i] = out;
131 
132  for (auto j : xrange(w)) {
133  out[3 * j + 0] = in[4 * j + 0];
134  out[3 * j + 1] = in[4 * j + 1];
135  out[3 * j + 2] = in[4 * j + 2];
136  }
137  }
138 
139  PNG::save(w, h, rowPointers, filename);
140 }
141 
143 {
144  SDL_GL_SwapWindow(window.get());
145 }
146 
147 std::unique_ptr<Layer> SDLGLVisibleSurface::createSnowLayer()
148 {
149  return std::make_unique<GLSnow>(getDisplay());
150 }
151 
153  Reactor& reactor, CommandConsole& console)
154 {
155  const bool openGL = true;
156  auto [width, height] = getLogicalSize();
157  return std::make_unique<OSDConsoleRenderer>(
158  reactor, console, width, height, openGL);
159 }
160 
161 std::unique_ptr<Layer> SDLGLVisibleSurface::createOSDGUILayer(OSDGUI& gui)
162 {
163  return std::make_unique<GLOSDGUILayer>(gui);
164 }
165 
166 std::unique_ptr<OutputSurface> SDLGLVisibleSurface::createOffScreenSurface()
167 {
168  return std::make_unique<SDLGLOffScreenSurface>(*this);
169 }
170 
171 void SDLGLVisibleSurface::VSyncObserver::update(const Setting& setting) noexcept
172 {
173  auto& visSurface = OUTER(SDLGLVisibleSurface, vSyncObserver);
174  auto& syncSetting = visSurface.getDisplay().getRenderSettings().getVSyncSetting();
175  assert(&setting == &syncSetting); (void)setting;
176 
177  // for now, we assume that adaptive vsync is the best kind of vsync, so when
178  // vsync is enabled, we attempt adaptive vsync.
179  int interval = syncSetting.getBoolean() ? -1 : 0;
180 
181  if ((SDL_GL_SetSwapInterval(interval) < 0) && (interval == -1)) {
182  // "Adaptive vsync" is not supported by all drivers. SDL
183  // documentation suggests to fallback to "regular vsync" in
184  // that case.
185  SDL_GL_SetSwapInterval(1);
186  }
187 }
188 
189 void SDLGLVisibleSurface::setViewPort(gl::ivec2 logicalSize, bool fullscreen)
190 {
191  gl::ivec2 physicalSize = [&] {
192 #ifndef __APPLE__
193  // On macos we set 'SDL_WINDOW_ALLOW_HIGHDPI', and in that case
194  // it's required to use SDL_GL_GetDrawableSize(), but then this
195  // 'fullscreen'-workaround/hack is counter-productive.
196  if (!fullscreen) {
197  // ??? When switching back from fullscreen to windowed mode,
198  // SDL_GL_GetDrawableSize() still returns the dimensions of the
199  // fullscreen window ??? Is this a bug ???
200  // But we know that in windowed mode, physical and logical size
201  // must be the same, so enforce that.
202  return logicalSize;
203  }
204 #endif
205  (void)fullscreen;
206  int w, h;
207  SDL_GL_GetDrawableSize(window.get(), &w, &h);
208  return gl::ivec2(w, h);
209  }();
210 
211  // The created surface may be larger than requested.
212  // If that happens, center the area that we actually use.
213  calculateViewPort(logicalSize, physicalSize);
214  auto [vx, vy] = getViewOffset();
215  auto [vw, vh] = getViewSize();
216  glViewport(vx, vy, vw, vh);
217 }
218 
220 {
221  setViewPort(getLogicalSize(), fullscreen);
222 }
223 
224 } // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
#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 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 fullScreenUpdated(bool fullscreen) override
void finish() override
When a complete frame is finished, call this method.
static void saveScreenshotGL(const OutputSurface &output, const std::string &filename)
SDLGLVisibleSurface(unsigned width, unsigned 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:142
std::optional< Context > context
Definition: GLContext.cc:9
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr const char *const filename
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:127
#define OUTER(type, member)
Definition: outer.hh:41
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
constexpr auto xrange(T e)
Definition: xrange.hh:155