OXYGENHUB
◆ main openmsx / src / video / GLUtil.cc
7.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "GLUtil.hh"
2
3
#include "File.hh"
4
#include "FileContext.hh"
5
#include "FileException.hh"
6
#include "InitException.hh"
7
8
#include "Version.hh"
9
10
#include <bit>
11
#include <cstdio>
12
#include <iostream>
13
14
using namespace openmsx;
15
16
using namespace std::literals;
17
18
namespace gl {
19
20
void checkGLError(std::string_view prefix)
21
{
22
	GLenum error = glGetError();
23
	if (error != GL_NO_ERROR) {
24
		// TODO this needs glu, but atm we don't link against glu (in windows)
25
		//std::string err = (const char*)gluErrorString(error);
26
		std::cerr << "GL error: " << prefix << ": " << int(error) << '\n';
27
		assert(false);
28
	}
29
}
30
31
32
// class Texture
33
34
Texture::Texture(bool interpolation, bool wrap)
35
{
36
	allocate();
37
	setInterpolation(interpolation);
38
	setWrapMode(wrap);
39
}
40
41
void Texture::allocate()
42
{
43
	glGenTextures(1, &textureId);
44
}
45
46
void Texture::reset()
47
{
48
	// Calling glDeleteTextures with a 0-texture-id is OK, it doesn't do
49
	// anything. So from that point of view we don't need this test. However
50
	// during initialization, before the openGL context is created, we
51
	// already create some Texture(null) objects. These can also be moved
52
	// around (std::move()), and then when the moved-from object gets
53
	// deleted that also calls this method. That should be fine as calling
54
	// glDeleteTextures() with 0 does nothing. EXCEPT that on macOS it does
55
	// crash when calling an openGL function before an openGL context is
56
	// created (it works fine on Linux and Windows).
57
	if (textureId) {
58
		glDeleteTextures(1, &textureId);
59
		textureId = 0;
60
	}
61
}
62
63
void Texture::setInterpolation(bool interpolation)
64
{
65
	bind();
66
	int mode = interpolation ? GL_LINEAR : GL_NEAREST;
67
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mode);
68
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mode);
69
}
70
71
void Texture::setWrapMode(bool wrap)
72
{
73
	bind();
74
	int mode = wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE;
75
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode);
76
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode);
77
}
78
79
80
// class ColorTexture
81
82
ColorTexture::ColorTexture(GLsizei width_, GLsizei height_)
83
{
84
	resize(width_, height_);
85
}
86
87
void ColorTexture::resize(GLsizei width_, GLsizei height_)
88
{
89
	width = width_;
90
	height = height_;
91
	bind();
92
	glTexImage2D(
93
		GL_TEXTURE_2D,    // target
94
		0,                // level
95
		GL_RGBA,          // internal format
96
		width,            // width
97
		height,           // height
98
		0,                // border
99
		GL_RGBA,          // format
100
		GL_UNSIGNED_BYTE, // type
101
		nullptr);         // data
102
}
103
104
105
// class FrameBufferObject
106
107
FrameBufferObject::FrameBufferObject(Texture& texture)
108
{
109
	glGenFramebuffers(1, &bufferId);
110
	push();
111
	glFramebufferTexture2D(GL_FRAMEBUFFER,
112
	                       GL_COLOR_ATTACHMENT0,
113
	                       GL_TEXTURE_2D, texture.textureId, 0);
114
	bool success = glCheckFramebufferStatus(GL_FRAMEBUFFER) ==
115
	               GL_FRAMEBUFFER_COMPLETE;
116
	pop();
117
	if (!success) {
118
		throw InitException(
119
			"Your OpenGL implementation support for "
120
			"framebuffer objects is too limited.");
121
	}
122
}
123
124
FrameBufferObject::~FrameBufferObject()
125
{
126
	pop();
127
	glDeleteFramebuffers(1, &bufferId);
128
}
129
130
void FrameBufferObject::push()
131
{
132
	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousId);
133
	glBindFramebuffer(GL_FRAMEBUFFER, bufferId);
134
}
135
136
void FrameBufferObject::pop()
137
{
138
	glBindFramebuffer(GL_FRAMEBUFFER, GLuint(previousId));
139
}
140
141
142
// class Shader
143
144
void Shader::init(GLenum type, std::string_view header, std::string_view filename)
145
{
146
	// Load shader source.
147
	std::string source;
148
	if constexpr (OPENGL_VERSION == OPENGL_ES_2_0) {
149
		source += "#version 100\n";
150
		if (type == GL_FRAGMENT_SHADER) {
151
			source += "#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
152
				"  precision highp float;\n"
153
				"#else\n"
154
				"  precision mediump float;\n"
155
				"#endif\n";
156
		}
157
	} else {
158
		source += "#version 110\n";
159
	}
160
	source += header;
161
	auto mmap = File(systemFileContext().resolve(tmpStrCat("shaders/", filename)))
162
	           .mmap<const char>();
163
	source.append(std::bit_cast<const char*>(mmap.data()), mmap.size());
164
165
	// Allocate shader handle.
166
	handle = glCreateShader(type);
167
	if (handle == 0) {
168
		throw MSXException("Failed to allocate shader");
169
	}
170
171
	// Set shader source.
172
	const char* sourcePtr = source.c_str();
173
	glShaderSource(handle, 1, &sourcePtr, nullptr);
174
175
	// Compile shader and print any errors and warnings.
176
	glCompileShader(handle);
177
178
	GLint compileStatus = GL_FALSE;
179
	glGetShaderiv(handle, GL_COMPILE_STATUS, &compileStatus);
180
	bool ok = compileStatus == GL_TRUE;
181
182
	GLint infoLogLength = 0;
183
	glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength);
184
	// note: the null terminator is included, so empty string has length 1
185
	if (!ok || (!Version::RELEASE && infoLogLength > 1)) {
186
		std::string infoLog(infoLogLength - 1, '\0');
187
		glGetShaderInfoLog(handle, infoLogLength, nullptr, infoLog.data());
188
		auto message = strCat((ok ? "Warning"sv : "Error"sv),
189
			"(s) compiling shader \"", filename, "\":\n",
190
			(infoLogLength > 1 ? infoLog.data() : "(no details available)\n"));
191
		if (ok) {
192
			std::cerr << message;
193
		} else {
194
			throw MSXException(message);
195
		}
196
	}
197
}
198
199
Shader::~Shader()
200
{
201
	glDeleteShader(handle); // ok to delete '0'
202
}
203
204
205
// class ShaderProgram
206
207
ShaderProgram::ShaderProgram()
208
	: handle(glCreateProgram())
209
{
210
	if (handle == 0) {
211
		throw MSXException("Failed to allocate program");
212
	}
213
}
214
215
ShaderProgram::~ShaderProgram()
216
{
217
	glDeleteProgram(handle);
218
}
219
220
bool ShaderProgram::isOK() const
221
{
222
	GLint linkStatus = GL_FALSE;
223
	glGetProgramiv(handle, GL_LINK_STATUS, &linkStatus);
224
	return linkStatus == GL_TRUE;
225
}
226
227
void ShaderProgram::attach(const Shader& shader)
228
{
229
	glAttachShader(handle, shader.handle);
230
}
231
232
void ShaderProgram::link()
233
{
234
	// Link the program and print any errors and warnings.
235
	glLinkProgram(handle);
236
	const bool ok = isOK();
237
238
	GLint infoLogLength = 0;
239
	glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength);
240
	// note: the null terminator is included, so empty string has length 1
241
	if (!ok || (!Version::RELEASE && infoLogLength > 1)) {
242
		std::string infoLog(infoLogLength - 1, '\0');
243
		glGetProgramInfoLog(handle, infoLogLength, nullptr, infoLog.data());
244
		auto message = strCat((ok ? "Warning" : "Error"), "(s) linking shader program:\n",
245
			(infoLogLength > 1 ? infoLog.data() : "(no details available)\n"));
246
		if (ok) {
247
			std::cerr << message;
248
		} else {
249
			throw MSXException(message);
250
		}
251
	}
252
}
253
254
void ShaderProgram::bindAttribLocation(unsigned index, const char* name)
255
{
256
	glBindAttribLocation(handle, index, name);
257
}
258
259
GLint ShaderProgram::getUniformLocation(const char* name) const
260
{
261
	// Sanity check on this program.
262
	if (!isOK()) return -1;
263
264
	return glGetUniformLocation(handle, name);
265
}
266
267
void ShaderProgram::activate() const
268
{
269
	glUseProgram(handle);
270
}
271
272
// only useful for debugging
273
void ShaderProgram::validate() const
274
{
275
	glValidateProgram(handle);
276
	GLint validateStatus = GL_FALSE;
277
	glGetProgramiv(handle, GL_VALIDATE_STATUS, &validateStatus);
278
	GLint infoLogLength = 0;
279
	glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength);
280
	// note: the null terminator is included, so empty string has length 1
281
	std::string infoLog(infoLogLength - 1, '\0');
282
	glGetProgramInfoLog(handle, infoLogLength, nullptr, infoLog.data());
283
	std::cout << "Validate "
284
	          << ((validateStatus == GL_TRUE) ? "OK"sv : "FAIL"sv)
285
	          << ": " << infoLog.data() << '\n';
286
}
287
288
289
// class BufferObject
290
291
BufferObject::BufferObject()
292
{
293
	glGenBuffers(1, &bufferId);
294
}
295
296
BufferObject::~BufferObject()
297
{
298
	glDeleteBuffers(1, &bufferId); // ok to delete 0-buffer
299
}
300
301
} // namespace gl