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