1
// Copyright (c) 2010, Thomas Goyne
2
// All rights reserved.
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of the Aegisub Group nor the names of its contributors
13
// may be used to endorse or promote products derived from this software
14
// without specific prior written permission.
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
28
// Aegisub Project http://www.aegisub.org/
30
// $Id: video_out_gl.cpp 4100 2010-02-14 18:09:50Z plorkyeran $
32
/// @file video_out_gl.cpp
33
/// @brief OpenGL based video renderer
47
// These must be included before local headers.
49
#include <OpenGL/GL.h>
50
#include <OpenGL/glu.h>
56
#include "video_out_gl.h"
58
#include "video_frame.h"
60
#define CHECK_INIT_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutInitException(_T(#cmd), err)
61
#define CHECK_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutRenderException(_T(#cmd), err)
63
/// @brief Structure tracking all precomputable information about a subtexture
64
struct VideoOutGL::TextureInfo {
96
/// @brief Test if a texture can be created
97
/// @param width The width of the texture
98
/// @param height The height of the texture
99
/// @param format The texture's format
100
/// @return Whether the texture could be created.
101
static bool TestTexture(int width, int height, GLint format) {
102
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
103
glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
104
while (glGetError()) { } // Silently swallow all errors as we don't care why it failed if it did
106
wxLogDebug(L"VideoOutGL::TestTexture: %dx%d\n", width, height);
110
VideoOutGL::VideoOutGL()
112
supportsRectangularTextures(false),
113
supportsGlClampToEdge(false),
126
/// @brief Runtime detection of required OpenGL capabilities
127
void VideoOutGL::DetectOpenGLCapabilities() {
128
if (maxTextureSize != 0) return;
130
// Test for supported internalformats
131
if (TestTexture(64, 64, GL_RGBA8)) internalFormat = GL_RGBA8;
132
else if (TestTexture(64, 64, GL_RGBA)) internalFormat = GL_RGBA;
133
else throw VideoOutInitException(L"Could not create a 64x64 RGB texture in any format.");
135
// Test for the maximum supported texture size
136
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
137
while (maxTextureSize > 64 && !TestTexture(maxTextureSize, maxTextureSize, internalFormat)) maxTextureSize >>= 1;
138
wxLogDebug(L"VideoOutGL::DetectOpenGLCapabilities: Maximum texture size is %dx%d\n", maxTextureSize, maxTextureSize);
140
// Test for rectangular texture support
141
supportsRectangularTextures = TestTexture(maxTextureSize, maxTextureSize >> 1, internalFormat);
144
/// @brief If needed, create the grid of textures for displaying frames of the given format
145
/// @param width The frame's width
146
/// @param height The frame's height
147
/// @param format The frame's format
148
/// @param bpp The frame's bytes per pixel
149
void VideoOutGL::InitTextures(int width, int height, GLenum format, int bpp, bool flipped) {
150
frameFlipped = flipped;
151
// Do nothing if the frame size and format are unchanged
152
if (width == frameWidth && height == frameHeight && format == frameFormat) return;
154
frameHeight = height;
155
frameFormat = format;
156
wxLogDebug(L"VideoOutGL::InitTextures: Video size: %dx%d\n", width, height);
158
DetectOpenGLCapabilities();
160
// Clean up old textures
161
if (textureIdList.size() > 0) {
162
CHECK_INIT_ERROR(glDeleteTextures(textureIdList.size(), &textureIdList[0]));
163
textureIdList.clear();
167
// Create the textures
168
int textureArea = maxTextureSize - 2;
169
textureRows = (int)ceil(double(height) / textureArea);
170
textureCols = (int)ceil(double(width) / textureArea);
171
textureIdList.resize(textureRows * textureCols);
172
textureList.resize(textureRows * textureCols);
173
CHECK_INIT_ERROR(glGenTextures(textureIdList.size(), &textureIdList[0]));
175
/* Unfortunately, we can't simply use one of the two standard ways to do
176
* tiled textures to work around texture size limits in OpenGL, due to our
177
* need to support Microsoft's OpenGL emulation for RDP/VPC/video card
178
* drivers that don't support OpenGL (such as the ones which Windows
179
* Update pushes for ATI cards in Windows 7). GL_CLAMP_TO_EDGE requires
180
* OpenGL 1.2, but the emulation only supports 1.1. GL_CLAMP + borders has
181
* correct results, but takes several seconds to render each frame. As a
182
* result, the code below essentially manually reimplements borders, by
183
* just not using the edge when mapping the texture onto a quad. The one
184
* exception to this is the texture edges which are also frame edges, as
185
* there does not appear to be a trivial way to mirror the edges, and the
186
* nontrivial ways are more complex that is worth to avoid a single row of
187
* slightly discolored pixels along the edges at zooms over 100%.
189
* Given a 64x64 maximum texture size:
190
* Quads touching the top of the frame are 63 pixels tall
191
* Quads touching the bottom of the frame are up to 63 pixels tall
192
* All other quads are 62 pixels tall
193
* Quads not on the top skip the first row of the texture
194
* Quads not on the bottom skip the last row of the texture
195
* Width behaves in the same way with respect to left/right edges
198
// Calculate the position information for each texture
199
int lastRow = textureRows - 1;
200
int lastCol = textureCols - 1;
201
for (int row = 0; row < textureRows; ++row) {
202
for (int col = 0; col < textureCols; ++col) {
203
TextureInfo& ti = textureList[row * textureCols + col];
205
// Width and height of the area read from the frame data
206
int sourceX = col * textureArea;
207
int sourceY = row * textureArea;
208
ti.sourceW = min(frameWidth - sourceX, maxTextureSize);
209
ti.sourceH = min(frameHeight - sourceY, maxTextureSize);
211
// Used instead of GL_PACK_SKIP_ROWS/GL_PACK_SKIP_PIXELS due to
212
// performance issues with the emulation
213
ti.dataOffset = sourceY * frameWidth * bpp + sourceX * bpp;
215
int textureHeight = SmallestPowerOf2(ti.sourceH);
216
int textureWidth = SmallestPowerOf2(ti.sourceW);
217
if (!supportsRectangularTextures) {
218
textureWidth = textureHeight = max(textureWidth, textureHeight);
221
// Location where this texture is placed
222
// X2/Y2 will be offscreen unless the video frame happens to
223
// exactly use all of the texture
224
ti.destX1 = sourceX + (col != 0);
225
ti.destY1 = sourceY + (row != 0);
226
ti.destX2 = sourceX + textureWidth - (col != lastCol);
227
ti.destY2 = sourceY + textureHeight - (row != lastRow);
229
// Portion of the texture actually used
230
ti.texTop = row == 0 ? 0 : 1.0f / textureHeight;
231
ti.texLeft = col == 0 ? 0 : 1.0f / textureWidth;
232
ti.texBottom = row == lastRow ? 1.0f : 1.0f - 1.0f / textureHeight;
233
ti.texRight = col == lastCol ? 1.0f : 1.0f - 1.0f / textureWidth;
235
ti.textureID = textureIdList[row * textureCols + col];
237
CreateTexture(textureWidth, textureHeight, ti, format);
242
void VideoOutGL::CreateTexture(int w, int h, const TextureInfo& ti, GLenum format) {
243
CHECK_INIT_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
244
CHECK_INIT_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, GL_UNSIGNED_BYTE, NULL));
245
wxLogDebug(L"VideoOutGL::InitTextures: Using texture size: %dx%d\n", w, h);
246
CHECK_INIT_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
247
CHECK_INIT_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
248
CHECK_INIT_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
249
CHECK_INIT_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
252
void VideoOutGL::UploadFrameData(const AegiVideoFrame& frame) {
253
if (frame.h == 0 || frame.w == 0) return;
255
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA;
256
InitTextures(frame.w, frame.h, format, frame.GetBpp(0), frame.flipped);
258
// Set the row length, needed to be able to upload partial rows
259
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.w));
261
for (unsigned i = 0; i < textureList.size(); i++) {
262
TextureInfo& ti = textureList[i];
264
CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
265
CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW,
266
ti.sourceH, format, GL_UNSIGNED_BYTE, frame.data[0] + ti.dataOffset));
269
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
271
void VideoOutGL::SetViewport(int x, int y, int width, int height) {
272
CHECK_ERROR(glViewport(x, y, width, height));
275
void VideoOutGL::Render(int sw, int sh) {
276
// Clear the frame buffer
277
CHECK_ERROR(glClearColor(0,0,0,0));
278
CHECK_ERROR(glClearStencil(0));
279
CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
282
CHECK_ERROR(glShadeModel(GL_FLAT));
283
CHECK_ERROR(glDisable(GL_BLEND));
285
CHECK_ERROR(glMatrixMode(GL_PROJECTION));
286
CHECK_ERROR(glLoadIdentity());
287
CHECK_ERROR(glPushMatrix());
289
CHECK_ERROR(glOrtho(0.0f, frameWidth, 0.0f, frameHeight, -1000.0f, 1000.0f));
292
CHECK_ERROR(glOrtho(0.0f, frameWidth, frameHeight, 0.0f, -1000.0f, 1000.0f));
295
// Render the current frame
296
CHECK_ERROR(glEnable(GL_TEXTURE_2D));
298
for (unsigned i = 0; i < textureList.size(); i++) {
299
TextureInfo& ti = textureList[i];
301
CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
302
CHECK_ERROR(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
305
glTexCoord2f(ti.texLeft, ti.texTop); glVertex2f(ti.destX1, ti.destY1);
306
glTexCoord2f(ti.texRight, ti.texTop); glVertex2f(ti.destX2, ti.destY1);
307
glTexCoord2f(ti.texRight, ti.texBottom); glVertex2f(ti.destX2, ti.destY2);
308
glTexCoord2f(ti.texLeft, ti.texBottom); glVertex2f(ti.destX1, ti.destY2);
310
if (GLenum err = glGetError()) throw VideoOutRenderException(L"GL_QUADS", err);
312
CHECK_ERROR(glDisable(GL_TEXTURE_2D));
314
CHECK_ERROR(glPopMatrix());
315
CHECK_ERROR(glOrtho(0.0f, sw, sh, 0.0f, -1000.0f, 1000.0f));
316
CHECK_ERROR(glMatrixMode(GL_MODELVIEW));
317
CHECK_ERROR(glLoadIdentity());
320
VideoOutGL::~VideoOutGL() {
321
if (textureIdList.size() > 0) {
322
glDeleteTextures(textureIdList.size(), &textureIdList[0]);