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 "CliComm.hh"
10 #include "PNG.hh"
11 #include "build-info.hh"
12 #include "MemBuffer.hh"
13 #include "outer.hh"
14 #include "vla.hh"
15 #include "InitException.hh"
16 #include "unreachable.hh"
17 #include <memory>
18 
19 #include "GLUtil.hh"
20 
21 namespace openmsx {
22 
24  unsigned width, unsigned height,
25  Display& display_,
26  RTScheduler& rtScheduler_,
27  EventDistributor& eventDistributor_,
28  InputEventGenerator& inputEventGenerator_,
29  CliComm& cliComm_,
30  VideoSystem& videoSystem_)
31  : SDLVisibleSurfaceBase(display_, rtScheduler_, eventDistributor_,
32  inputEventGenerator_, cliComm_, videoSystem_)
33 {
34  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
35  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
36  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
37 #if OPENGL_VERSION == OPENGL_ES_2_0
38  #define VERSION_STRING "openGL ES 2.0"
39  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
40  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
41  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
42 #elif OPENGL_VERSION == OPENGL_2_1
43  #define VERSION_STRING "openGL 2.1"
44  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
45  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
46  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
47 #elif OPENGL_VERSION == OPENGL_3_3
48  #define VERSION_STRING "openGL 3.3"
49  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
50  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
51  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
52 #endif
53 
54  int flags = SDL_WINDOW_OPENGL;
55  //flags |= SDL_RESIZABLE;
56  createSurface(width, height, flags);
57 
58  glContext = SDL_GL_CreateContext(window.get());
59  if (!glContext) {
60  throw InitException(
61  "Failed to create " VERSION_STRING " context: ", SDL_GetError());
62  }
63 
64  // From the glew documentation:
65  // GLEW obtains information on the supported extensions from the
66  // graphics driver. Experimental or pre-release drivers, however,
67  // might not report every available extension through the standard
68  // mechanism, in which case GLEW will report it unsupported. To
69  // circumvent this situation, the glewExperimental global switch can
70  // be turned on by setting it to GL_TRUE before calling glewInit(),
71  // which ensures that all extensions with valid entry points will be
72  // exposed.
73  // The 'glewinfo' utility also sets this flag before reporting results,
74  // so I believe it would cause less confusion to do the same here.
75  glewExperimental = GL_TRUE;
76 
77  // Initialise GLEW library.
78  GLenum glew_error = glewInit();
79  if (glew_error != GLEW_OK) {
80  throw InitException(
81  "Failed to init GLEW: ",
82  reinterpret_cast<const char*>(
83  glewGetErrorString(glew_error)));
84  }
85 
86  bool fullscreen = getDisplay().getRenderSettings().getFullScreen();
87  setViewPort(gl::ivec2(width, height), fullscreen); // set initial values
88 
90  gl::context = std::make_unique<gl::Context>(width, height);
91 
93  // set initial value
94  vSyncObserver.update(getDisplay().getRenderSettings().getVSyncSetting());
95 
96 #if OPENGL_VERSION == OPENGL_3_3
97  // We don't (yet/anymore) use VAO, but apparently it's required in openGL 3.3.
98  // Luckily this workaround is sufficient: create one global VAO and then don't care anymore.
99  GLuint vao;
100  glGenVertexArrays(1, &vao);
101  glBindVertexArray(vao);
102 #endif
103 }
104 
106 {
107  getDisplay().getRenderSettings().getVSyncSetting().detach(vSyncObserver);
108 
109  gl::context.reset();
110  SDL_GL_DeleteContext(glContext);
111 }
112 
114 {
115  saveScreenshotGL(*this, filename);
116 }
117 
119  const OutputSurface& output, const std::string& filename)
120 {
121  auto [x, y] = output.getViewOffset();
122  auto [w, h] = output.getViewSize();
123 
124  // OpenGL ES only supports reading RGBA (not RGB)
125  MemBuffer<uint8_t> buffer(w * h * 4);
126  glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
127 
128  // perform in-place conversion of RGBA -> RGB
129  VLA(const void*, rowPointers, h);
130  for (auto i : xrange(h)) {
131  uint8_t* out = &buffer[w * 4 * i];
132  const uint8_t* in = out;
133  rowPointers[h - 1 - i] = out;
134 
135  for (auto j : xrange(w)) {
136  out[3 * j + 0] = in[4 * j + 0];
137  out[3 * j + 1] = in[4 * j + 1];
138  out[3 * j + 2] = in[4 * j + 2];
139  }
140  }
141 
142  PNG::save(w, h, rowPointers, filename);
143 }
144 
146 {
147  SDL_GL_SwapWindow(window.get());
148 }
149 
150 std::unique_ptr<Layer> SDLGLVisibleSurface::createSnowLayer()
151 {
152  return std::make_unique<GLSnow>(getDisplay());
153 }
154 
156  Reactor& reactor, CommandConsole& console)
157 {
158  const bool openGL = true;
159  auto [width, height] = getLogicalSize();
160  return std::make_unique<OSDConsoleRenderer>(
161  reactor, console, width, height, openGL);
162 }
163 
164 std::unique_ptr<Layer> SDLGLVisibleSurface::createOSDGUILayer(OSDGUI& gui)
165 {
166  return std::make_unique<GLOSDGUILayer>(gui);
167 }
168 
169 std::unique_ptr<OutputSurface> SDLGLVisibleSurface::createOffScreenSurface()
170 {
171  return std::make_unique<SDLGLOffScreenSurface>(*this);
172 }
173 
174 void SDLGLVisibleSurface::VSyncObserver::update(const Setting& setting) noexcept
175 {
176  auto& visSurface = OUTER(SDLGLVisibleSurface, vSyncObserver);
177  auto& syncSetting = visSurface.getDisplay().getRenderSettings().getVSyncSetting();
178  assert(&setting == &syncSetting); (void)setting;
179 
180  // for now, we assume that adaptive vsync is the best kind of vsync, so when
181  // vsync is enabled, we attempt adaptive vsync.
182  int interval = syncSetting.getBoolean() ? -1 : 0;
183 
184  if ((SDL_GL_SetSwapInterval(interval) < 0) && (interval == -1)) {
185  // "Adaptive vsync" is not supported by all drivers. SDL
186  // documentation suggests to fallback to "regular vsync" in
187  // that case.
188  SDL_GL_SetSwapInterval(1);
189  }
190 }
191 
192 void SDLGLVisibleSurface::setViewPort(gl::ivec2 logicalSize, bool fullscreen)
193 {
194  gl::ivec2 physicalSize = [&] {
195 #ifndef __APPLE__
196  // On macos we set 'SDL_WINDOW_ALLOW_HIGHDPI', and in that case
197  // it's required to use SDL_GL_GetDrawableSize(), but then this
198  // 'fullscreen'-workaround/hack is counter-productive.
199  if (!fullscreen) {
200  // ??? When switching back from fullscreen to windowed mode,
201  // SDL_GL_GetDrawableSize() still returns the dimensions of the
202  // fullscreen window ??? Is this a bug ???
203  // But we know that in windowed mode, physical and logical size
204  // must be the same, so enforce that.
205  return logicalSize;
206  }
207 #endif
208  (void)fullscreen;
209  int w, h;
210  SDL_GL_GetDrawableSize(window.get(), &w, &h);
211  return gl::ivec2(w, h);
212  }();
213 
214  // The created surface may be larger than requested.
215  // If that happens, center the area that we actually use.
216  calculateViewPort(logicalSize, physicalSize);
217  auto [vx, vy] = getViewOffset();
218  auto [vw, vh] = getViewSize();
219  glViewport(vx, vy, vw, vh);
220 }
221 
223 {
224  setViewPort(getLogicalSize(), fullscreen);
225 }
226 
227 } // namespace openmsx
#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:67
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::unique_ptr< Context > context
Definition: GLContext.cc:9
This file implemented 3 utility functions:
Definition: Autofire.cc:5
constexpr const char *const filename
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:124
#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