Changeset - r24909:194a0b94e9cb
[Not reviewed]
master
0 2 0
Michael Lutz - 3 years ago 2021-01-16 15:43:43
michi@icosahedron.de
Codechange: [OpenGL] Use persistently mapped pixel buffers when supported.
2 files changed with 147 insertions and 12 deletions:
0 comments (0 inline, 0 general)
src/video/opengl.cpp
Show inline comments
 
@@ -8,12 +8,17 @@
 
 */
 

	
 
/** @file opengl_v.cpp OpenGL video driver support. */
 

	
 
#include "../stdafx.h"
 

	
 
/* Define to disable buffer syncing. Will increase max fast forward FPS but produces artifacts. Mainly useful for performance testing. */
 
// #define NO_GL_BUFFER_SYNC
 
/* Define to enable persistent buffer mapping on AMD GPUs. */
 
// #define GL_MAP_PERSISTENT_AMD
 

	
 
#if defined(_WIN32)
 
#	include <windows.h>
 
#endif
 

	
 
#if defined(__APPLE__)
 
#	include <OpenGL/gl3.h>
 
@@ -47,12 +52,18 @@ static PFNGLGENBUFFERSPROC _glGenBuffers
 
static PFNGLDELETEBUFFERSPROC _glDeleteBuffers;
 
static PFNGLBINDBUFFERPROC _glBindBuffer;
 
static PFNGLBUFFERDATAPROC _glBufferData;
 
static PFNGLMAPBUFFERPROC _glMapBuffer;
 
static PFNGLUNMAPBUFFERPROC _glUnmapBuffer;
 

	
 
static PFNGLBUFFERSTORAGEPROC _glBufferStorage;
 
static PFNGLMAPBUFFERRANGEPROC _glMapBufferRange;
 
static PFNGLCLIENTWAITSYNCPROC _glClientWaitSync;
 
static PFNGLFENCESYNCPROC _glFenceSync;
 
static PFNGLDELETESYNCPROC _glDeleteSync;
 

	
 
static PFNGLGENVERTEXARRAYSPROC _glGenVertexArrays;
 
static PFNGLDELETEVERTEXARRAYSPROC _glDeleteVertexArrays;
 
static PFNGLBINDVERTEXARRAYPROC _glBindVertexArray;
 

	
 
static PFNGLCREATEPROGRAMPROC _glCreateProgram;
 
static PFNGLDELETEPROGRAMPROC _glDeleteProgram;
 
@@ -287,12 +298,37 @@ static bool BindShaderExtensions()
 
		_glCreateShader != nullptr && _glDeleteShader != nullptr && _glShaderSource != nullptr && _glCompileShader != nullptr && _glAttachShader != nullptr &&
 
		_glGetShaderiv != nullptr && _glGetShaderInfoLog != nullptr && _glGetUniformLocation != nullptr && _glUniform1i != nullptr && _glUniform1f != nullptr &&
 
		_glUniform2f != nullptr && _glUniform4f != nullptr && _glGetAttribLocation != nullptr && _glEnableVertexAttribArray != nullptr && _glDisableVertexAttribArray != nullptr &&
 
		_glVertexAttribPointer != nullptr;
 
}
 

	
 
/** Bind extension functions for persistent buffer mapping. */
 
static bool BindPersistentBufferExtensions()
 
{
 
	/* Optional functions for persistent buffer mapping. */
 
	if (IsOpenGLVersionAtLeast(3, 0)) {
 
		_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)GetOGLProcAddress("glMapBufferRange");
 
	}
 
	if (IsOpenGLVersionAtLeast(4, 4) || IsOpenGLExtensionSupported("GL_ARB_buffer_storage")) {
 
		_glBufferStorage = (PFNGLBUFFERSTORAGEPROC)GetOGLProcAddress("glBufferStorage");
 
	}
 
#ifndef NO_GL_BUFFER_SYNC
 
	if (IsOpenGLVersionAtLeast(3, 2) || IsOpenGLExtensionSupported("GL_ARB_sync")) {
 
		_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)GetOGLProcAddress("glClientWaitSync");
 
		_glFenceSync = (PFNGLFENCESYNCPROC)GetOGLProcAddress("glFenceSync");
 
		_glDeleteSync = (PFNGLDELETESYNCPROC)GetOGLProcAddress("glDeleteSync");
 
	}
 
#endif
 

	
 
	return _glMapBufferRange != nullptr && _glBufferStorage != nullptr
 
#ifndef NO_GL_BUFFER_SYNC
 
		&& _glClientWaitSync != nullptr && _glFenceSync != nullptr && _glDeleteSync != nullptr
 
#endif
 
		;
 
}
 

	
 
/** Callback to receive OpenGL debug messages. */
 
void APIENTRY DebugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
 
{
 
	/* Make severity human readable. */
 
	const char *severity_str = "";
 
	switch (severity) {
 
@@ -437,12 +473,31 @@ const char *OpenGLBackend::Init()
 
	if (!BindVBAExtension()) return "Failed to bind VBA extension functions";
 
	/* Check for shader objects. */
 
	if (!IsOpenGLVersionAtLeast(2, 0) && (!IsOpenGLExtensionSupported("GL_ARB_shader_objects") || !IsOpenGLExtensionSupported("GL_ARB_fragment_shader") || !IsOpenGLExtensionSupported("GL_ARB_vertex_shader"))) return "No shader support";
 
	if (!BindShaderExtensions()) return "Failed to bind shader extension functions";
 
	if (IsOpenGLVersionAtLeast(3, 2) && _glBindFragDataLocation == nullptr) return "OpenGL claims to support version 3.2 but doesn't have glBindFragDataLocation";
 

	
 
	this->persistent_mapping_supported = IsOpenGLVersionAtLeast(3, 0) && (IsOpenGLVersionAtLeast(4, 4) || IsOpenGLExtensionSupported("GL_ARB_buffer_storage"));
 
#ifndef NO_GL_BUFFER_SYNC
 
	this->persistent_mapping_supported = this->persistent_mapping_supported && (IsOpenGLVersionAtLeast(3, 2) || IsOpenGLExtensionSupported("GL_ARB_sync"));
 
#endif
 

	
 
#ifndef GL_MAP_PERSISTENT_AMD
 
	if (this->persistent_mapping_supported && (strstr(vend, "AMD") != nullptr || strstr(renderer, "Radeon") != nullptr)) {
 
		/* AMD GPUs seem to perform badly with persistent buffer mapping, disable it for them. */
 
		DEBUG(driver, 3, "OpenGL: Detected AMD GPU, not using persistent buffer mapping due to performance problems");
 
		this->persistent_mapping_supported = false;
 
	}
 
#endif
 

	
 
	if (this->persistent_mapping_supported && !BindPersistentBufferExtensions()) {
 
		DEBUG(driver, 1, "OpenGL claims to support persistent buffer mapping but doesn't export all functions, not using persistent mapping.");
 
		this->persistent_mapping_supported = false;
 
	}
 
	if (this->persistent_mapping_supported) DEBUG(driver, 3, "OpenGL: Using persistent buffer mapping");
 

	
 
	/* Check available texture units. */
 
	GLint max_tex_units = 0;
 
	glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_tex_units);
 
	if (max_tex_units < 3) return "Not enough simultaneous textures supported";
 

	
 
	DEBUG(driver, 2, "OpenGL shading language version: %s, texture units = %d", (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION), (int)max_tex_units);
 
@@ -703,15 +758,24 @@ bool OpenGLBackend::Resize(int w, int h,
 

	
 
	int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
 
	int pitch = Align(w, 4);
 

	
 
	glViewport(0, 0, w, h);
 

	
 
	/* Re-allocate video buffer texture and backing store. */
 
	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
	_glBufferData(GL_PIXEL_UNPACK_BUFFER, pitch * h * bpp / 8, nullptr, GL_DYNAMIC_READ); // Buffer content has to persist from frame to frame and is read back by the blitter, which means a READ usage hint.
 
	this->vid_buffer = nullptr;
 
	if (this->persistent_mapping_supported) {
 
		_glDeleteBuffers(1, &this->vid_pbo);
 
		_glGenBuffers(1, &this->vid_pbo);
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
		_glBufferStorage(GL_PIXEL_UNPACK_BUFFER, pitch * h * bpp / 8, nullptr, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT | GL_CLIENT_STORAGE_BIT);
 
	} else {
 
		/* Re-allocate video buffer texture and backing store. */
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
		_glBufferData(GL_PIXEL_UNPACK_BUFFER, pitch * h * bpp / 8, nullptr, GL_DYNAMIC_DRAW);
 
	}
 

	
 
	if (bpp == 32) {
 
		/* Initialize backing store alpha to opaque for 32bpp modes. */
 
		Colour black(0, 0, 0);
 
		uint32 *buf = (uint32 *)_glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_READ_WRITE);
 
		for (int i = 0; i < pitch * h; i++) {
 
			*buf++ = black.data;
 
@@ -731,19 +795,34 @@ bool OpenGLBackend::Resize(int w, int h,
 
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
 
			break;
 
	}
 

	
 
	/* Does this blitter need a separate animation buffer? */
 
	if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
		_glBufferData(GL_PIXEL_UNPACK_BUFFER, pitch * h, NULL, GL_DYNAMIC_READ); // Buffer content has to persist from frame to frame and is read back by the blitter, which means a READ usage hint.
 
		this->anim_buffer = nullptr;
 
		if (this->persistent_mapping_supported) {
 
			_glDeleteBuffers(1, &this->anim_pbo);
 
			_glGenBuffers(1, &this->anim_pbo);
 
			_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
			_glBufferStorage(GL_PIXEL_UNPACK_BUFFER, pitch * h, nullptr, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT | GL_CLIENT_STORAGE_BIT);
 
		} else {
 
			_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
			_glBufferData(GL_PIXEL_UNPACK_BUFFER, pitch * h, nullptr, GL_DYNAMIC_DRAW);
 
		}
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
 

	
 
		glBindTexture(GL_TEXTURE_2D, this->anim_texture);
 
		glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
 
		glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
 
	} else {
 
		if (this->anim_buffer != nullptr) {
 
			_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
			_glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
 
			_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
 
			this->anim_buffer = nullptr;
 
		}
 

	
 
		/* Allocate dummy texture that always reads as 0 == no remap. */
 
		uint dummy = 0;
 
		glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
 
		glBindTexture(GL_TEXTURE_2D, this->anim_texture);
 
		glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &dummy);
 
	}
 
@@ -851,38 +930,70 @@ void OpenGLBackend::ClearCursorCache()
 
/**
 
 * Get a pointer to the memory for the video driver to draw to.
 
 * @return Pointer to draw on.
 
 */
 
void *OpenGLBackend::GetVideoBuffer()
 
{
 
	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
	return _glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_READ_WRITE);
 
#ifndef NO_GL_BUFFER_SYNC
 
	if (this->sync_vid_mapping != nullptr) _glClientWaitSync(this->sync_vid_mapping, GL_SYNC_FLUSH_COMMANDS_BIT, 10000000);
 
#endif
 

	
 
	if (!this->persistent_mapping_supported) {
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
		this->vid_buffer = _glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_READ_WRITE);
 
	} else if (this->vid_buffer == nullptr) {
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
		this->vid_buffer = _glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _screen.pitch * _screen.height * BlitterFactory::GetCurrentBlitter()->GetScreenDepth() / 8, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
 
	}
 

	
 
	return this->vid_buffer;
 
}
 

	
 
/**
 
 * Get a pointer to the memory for the separate animation buffer.
 
 * @return Pointer to draw on.
 
 */
 
uint8 *OpenGLBackend::GetAnimBuffer()
 
{
 
	if (this->anim_pbo == 0) return nullptr;
 

	
 
	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
	return (uint8 *)_glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_READ_WRITE);
 
#ifndef NO_GL_BUFFER_SYNC
 
	if (this->sync_anim_mapping != nullptr) _glClientWaitSync(this->sync_anim_mapping, GL_SYNC_FLUSH_COMMANDS_BIT, 10000000);
 
#endif
 

	
 
	if (!this->persistent_mapping_supported) {
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
		this->anim_buffer = _glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_READ_WRITE);
 
	} else if (this->anim_buffer == nullptr) {
 
		_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
		this->anim_buffer = _glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _screen.pitch * _screen.height, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
 
	}
 

	
 
	return (uint8 *)this->anim_buffer;
 
}
 

	
 
/**
 
 * Update video buffer texture after the video buffer was filled.
 
 * @param update_rect Rectangle encompassing the dirty region of the video buffer.
 
 */
 
void OpenGLBackend::ReleaseVideoBuffer(const Rect &update_rect)
 
{
 
	assert(this->vid_pbo != 0);
 

	
 
	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
 
	_glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
 
	if (!this->persistent_mapping_supported) {
 
		_glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
 
		this->vid_buffer = nullptr;
 
	}
 

	
 
#ifndef NO_GL_BUFFER_SYNC
 
	if (this->persistent_mapping_supported) {
 
		_glDeleteSync(this->sync_vid_mapping);
 
		this->sync_vid_mapping = nullptr;
 
	}
 
#endif
 

	
 
	/* Update changed rect of the video buffer texture. */
 
	if (!IsEmptyRect(update_rect)) {
 
		_glActiveTexture(GL_TEXTURE0);
 
		glBindTexture(GL_TEXTURE_2D, this->vid_texture);
 
		glPixelStorei(GL_UNPACK_ROW_LENGTH, _screen.pitch);
 
@@ -892,32 +1003,50 @@ void OpenGLBackend::ReleaseVideoBuffer(c
 
				break;
 

	
 
			default:
 
				glTexSubImage2D(GL_TEXTURE_2D, 0, update_rect.left, update_rect.top, update_rect.right - update_rect.left, update_rect.bottom - update_rect.top, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)(size_t)(update_rect.top * _screen.pitch * 4 + update_rect.left * 4));
 
				break;
 
		}
 

	
 
#ifndef NO_GL_BUFFER_SYNC
 
		if (this->persistent_mapping_supported) this->sync_vid_mapping = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 
#endif
 
	}
 
}
 

	
 
/**
 
 * Update animation buffer texture after the animation buffer was filled.
 
 * @param update_rect Rectangle encompassing the dirty region of the animation buffer.
 
 */
 
void OpenGLBackend::ReleaseAnimBuffer(const Rect &update_rect)
 
{
 
	if (this->anim_pbo == 0) return;
 

	
 
	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->anim_pbo);
 
	_glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
 
	if (!this->persistent_mapping_supported) {
 
		_glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
 
		this->anim_buffer = nullptr;
 
	}
 

	
 
#ifndef NO_GL_BUFFER_SYNC
 
	if (this->persistent_mapping_supported) {
 
		_glDeleteSync(this->sync_anim_mapping);
 
		this->sync_anim_mapping = nullptr;
 
	}
 
#endif
 

	
 
	/* Update changed rect of the video buffer texture. */
 
	if (update_rect.left != update_rect.right) {
 
		_glActiveTexture(GL_TEXTURE0);
 
		glBindTexture(GL_TEXTURE_2D, this->anim_texture);
 
		glPixelStorei(GL_UNPACK_ROW_LENGTH, _screen.pitch);
 
		glTexSubImage2D(GL_TEXTURE_2D, 0, update_rect.left, update_rect.top, update_rect.right - update_rect.left, update_rect.bottom - update_rect.top, GL_RED, GL_UNSIGNED_BYTE, (GLvoid *)(size_t)(update_rect.top * _screen.pitch + update_rect.left));
 

	
 
#ifndef NO_GL_BUFFER_SYNC
 
		if (this->persistent_mapping_supported) this->sync_anim_mapping = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 
#endif
 
	}
 
}
 

	
 
/* virtual */ Sprite *OpenGLBackend::Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *allocator)
 
{
 
	/* Allocate and construct sprite data. */
src/video/opengl.h
Show inline comments
 
@@ -28,20 +28,26 @@ class OpenGLSprite;
 

	
 
/** Platform-independent back-end class for OpenGL video drivers. */
 
class OpenGLBackend : public ZeroedMemoryAllocator, SpriteEncoder {
 
private:
 
	static OpenGLBackend *instance; ///< Singleton instance pointer.
 

	
 
	bool persistent_mapping_supported; ///< Persistent pixel buffer mapping supported.
 
	GLsync sync_vid_mapping;           ///< Sync object for the persistently mapped video buffer.
 
	GLsync sync_anim_mapping;          ///< Sync object for the persistently mapped animation buffer.
 

	
 
	void *vid_buffer;   ///< Pointer to the mapped video buffer.
 
	GLuint vid_pbo;     ///< Pixel buffer object storing the memory used for the video driver to draw to.
 
	GLuint vid_texture; ///< Texture handle for the video buffer texture.
 
	GLuint vid_program; ///< Shader program for rendering a RGBA video buffer.
 
	GLuint pal_program; ///< Shader program for rendering a paletted video buffer.
 
	GLuint vao_quad;    ///< Vertex array object storing the rendering state for the fullscreen quad.
 
	GLuint vbo_quad;    ///< Vertex buffer with a fullscreen quad.
 
	GLuint pal_texture; ///< Palette lookup texture.
 

	
 
	void *anim_buffer;   ///< Pointer to the mapped animation buffer.
 
	GLuint anim_pbo;     ///< Pixel buffer object storing the memory used for the animation buffer.
 
	GLuint anim_texture; ///< Texture handle for the animation buffer texture.
 

	
 
	GLuint remap_program;    ///< Shader program for blending and rendering a RGBA + remap texture.
 
	GLint  remap_sprite_loc; ///< Uniform location for sprite parameters.
 
	GLint  remap_screen_loc; ///< Uniform location for screen size;
0 comments (0 inline, 0 general)