Backport rewritten video display to 2.1.8. Merges revisions 3615,3617,3620,3623,3628-3631,3666,3711,3714,3717,3723,3726,3729,3739 (VideoOutGL); 3190,3201,3526,3613 (exception.h) from trunk.

Originally committed to SVN as r3741.
This commit is contained in:
Thomas Goyne 2009-10-27 20:41:11 +00:00
parent 5f5c5569b8
commit cb1127af2a
9 changed files with 690 additions and 289 deletions

View File

@ -1827,6 +1827,10 @@
RelativePath="..\..\src\include\aegisub\audio_provider.h"
>
</File>
<File
RelativePath="..\..\src\include\aegisub\exception.h"
>
</File>
<File
RelativePath="..\..\src\include\aegisub\spellchecker.h"
>
@ -1903,6 +1907,14 @@
RelativePath="..\..\src\video_display.h"
>
</File>
<File
RelativePath="..\..\src\video_out_gl.cpp"
>
</File>
<File
RelativePath="..\..\src\video_out_gl.h"
>
</File>
<File
RelativePath="..\..\src\video_slider.cpp"
>

View File

@ -338,6 +338,7 @@ aegisub_2_1_SOURCES = \
video_context.cpp \
video_display.cpp \
video_frame.cpp \
video_out_gl.ccp \
video_provider_cache.cpp \
video_provider_dummy.cpp \
video_provider_manager.cpp \

View File

@ -0,0 +1,137 @@
// Copyright (c) 2009, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <wx/string.h>
#pragma once
namespace Aegisub {
// Base class for exceptions
// No public creators, all exceptions throws must be specific
class Exception {
wxString message;
Exception *inner;
protected:
Exception(const wxString &msg, const Exception *inr = 0)
: message(msg)
, inner(0)
{
if (inr)
inner = inr->Copy();
}
Exception(); // not implemented, not wanted
virtual ~Exception() { if (inner) delete inner; }
public:
// Error message for outer exception
virtual wxString GetMessage() const { return message; }
// Error message for outer exception, and chained message for inner exception
wxString GetChainedMessage() const { if (inner) return inner->GetChainedMessage() + _T("\r\n") + GetMessage(); else return GetMessage(); }
// Name of exception class, should only be implemented by specific classes
virtual const wxChar * GetName() const = 0;
operator const wxChar * () { return GetMessage().c_str(); }
operator wxString () { return GetMessage(); }
/// @brief Create a copy of the exception allocated on the heap
/// @return A heap-allocated exception object
///
/// All deriving classes must implement this explicitly to avoid losing
/// information in the duplication.
virtual Exception *Copy() const = 0;
};
// Macro to quickly add location information to an error message
#define AG_WHERE _T(" (at ") _T(__FILE__) _T(":") _T(#__LINE__) _T(")")
// Macros to define basic exception classes that do nothing fancy
// These should always be used inside the Aegisub namespace
#define DEFINE_SIMPLE_EXCEPTION_NOINNER(classname,baseclass,displayname) \
class classname : public baseclass { \
public: \
classname(const wxString &msg) : baseclass(msg) { } \
const wxChar * GetName() const { return _T(displayname); } \
Exception * Copy() const { return new classname(*this); } \
};
#define DEFINE_SIMPLE_EXCEPTION(classname,baseclass,displayname) \
class classname : public baseclass { \
public: \
classname(const wxString &msg, Exception *inner) : baseclass(msg, inner) { } \
const wxChar * GetName() const { return _T(displayname); } \
Exception * Copy() const { return new classname(*this); } \
};
#define DEFINE_BASE_EXCEPTION_NOINNER(classname,baseclass) \
class classname : public baseclass { \
public: \
classname(const wxString &msg) : baseclass(msg) { } \
};
#define DEFINE_BASE_EXCEPTION(classname,baseclass) \
class classname : public baseclass { \
public: \
classname(const wxString &msg, Exception *inner) : baseclass(msg, inner) { } \
};
// Exception for "user cancel" events
// I.e. when we want to abort an operation because the user requested that we do so
// Not actually an error and should not be handled as such
DEFINE_SIMPLE_EXCEPTION_NOINNER(UserCancelException,Exception,"nonerror/user_cancel")
// Errors that should never happen and point to some invalid assumption in the code
DEFINE_SIMPLE_EXCEPTION(InternalError, Exception, "internal_error")
// Some error related to the filesystem
// These should always be original causes and as such do not support inner exceptions
DEFINE_BASE_EXCEPTION_NOINNER(FileSystemError,Exception)
// A file can't be accessed for some reason
DEFINE_SIMPLE_EXCEPTION_NOINNER(FileNotAccessibleError,FileSystemError,"filesystem/not_accessible")
// A file isn't accessible because it doesn't exist
class FileNotFoundError : public FileNotAccessibleError {
public:
FileNotFoundError(const wxString &filename) : FileNotAccessibleError(wxString(_T("File not found: ")) + filename) { }
const wxChar * GetName() const { return _T("filesystem/not_accessible/not_found"); }
// Not documented, see Aegisub::Exception class
Exception * Copy() const { return new FileNotFoundError(*this); } \
};
// A problem with some input data
DEFINE_BASE_EXCEPTION(InvalidInputException,Exception)
// There is no "generic exception" class, everything must be a specific one
// Define new classes if none fit the error you're reporting
};

View File

@ -352,28 +352,13 @@ void VideoContext::RemoveDisplay(VideoDisplay *display) {
// Update displays
void VideoContext::UpdateDisplays(bool full) {
for (std::list<VideoDisplay*>::iterator cur=displayList.begin();cur!=displayList.end();cur++) {
// Get display
VideoDisplay *display = *cur;
// Update slider
if (full) {
display->UpdateSize();
display->ControlSlider->SetRange(0,GetLength()-1);
}
display->ControlSlider->SetValue(GetFrameN());
//display->ControlSlider->Update();
display->UpdatePositionDisplay();
// If not shown, don't update the display itself
if (!display->IsShownOnScreen()) continue;
// Update visual controls
if (display->visual) display->visual->Refresh();
// Update controls
//display->Refresh();
//display->Update();
display->Render();
display->SetFrame(GetFrameN());
}
// Update audio display
@ -384,7 +369,6 @@ void VideoContext::UpdateDisplays(bool full) {
}
}
/////////////////////
// Refresh subtitles
void VideoContext::Refresh (bool video, bool subtitles) {
@ -434,7 +418,6 @@ void VideoContext::JumpToFrame(int n) {
try {
// Set frame number
frame_n = n;
GetFrameAsTexture(n);
// Display
UpdateDisplays(false);
@ -526,122 +509,9 @@ AegiVideoFrame VideoContext::GetFrame(int n,bool raw) {
else return frame;
}
///////////////////////////
// Get GL Texture of frame
GLuint VideoContext::GetFrameAsTexture(int n) {
// Already uploaded
if (n == lastFrame || n == -1) return lastTex;
// Get frame
AegiVideoFrame frame = GetFrame(n);
// Set frame
lastFrame = n;
// Set context
#ifdef __APPLE__
GetGLContext(displayList.front())->SetCurrent();
#else
GetGLContext(displayList.front())->SetCurrent(*displayList.front());
#endif
glEnable(GL_TEXTURE_2D);
if (glGetError() != 0) throw _T("Error enabling texture.");
// Image type
GLenum format = GL_LUMINANCE;
if (frame.format == FORMAT_RGB32) {
if (frame.invertChannels) format = GL_BGRA_EXT;
else format = GL_RGBA;
}
else if (frame.format == FORMAT_RGB24) {
if (frame.invertChannels) format = GL_BGR_EXT;
else format = GL_RGB;
}
else if (frame.format == FORMAT_YV12) {
format = GL_LUMINANCE;
}
isInverted = frame.flipped;
if (lastTex == 0) {
// Enable
glShadeModel(GL_FLAT);
// Generate texture with GL
//glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &lastTex);
if (glGetError() != 0) throw _T("Error generating texture.");
glBindTexture(GL_TEXTURE_2D, lastTex);
if (glGetError() != 0) throw _T("Error binding texture.");
// Texture parameters
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
if (glGetError() != 0) throw _T("Error setting min_filter texture parameter.");
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
if (glGetError() != 0) throw _T("Error setting mag_filter texture parameter.");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
if (glGetError() != 0) throw _T("Error setting wrap_s texture parameter.");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
if (glGetError() != 0) throw _T("Error setting wrap_t texture parameter.");
// Allocate texture
int height = frame.h;
if (frame.format == FORMAT_YV12) height = height * 3 / 2;
int tw = SmallestPowerOf2(MAX(frame.pitch[0]/frame.GetBpp(0),frame.pitch[1]+frame.pitch[2]));
int th = SmallestPowerOf2(height);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,tw,th,0,format,GL_UNSIGNED_BYTE,NULL);
if (glGetError() != 0) {
tw = MAX(tw,th);
th = tw;
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,tw,th,0,format,GL_UNSIGNED_BYTE,NULL);
if (glGetError() != 0) {
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,tw,th,0,format,GL_UNSIGNED_BYTE,NULL);
if (glGetError() != 0) throw _T("Error allocating texture.");
}
}
texW = float(frame.w)/float(tw);
texH = float(frame.h)/float(th);
// Set texture
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
//if (glGetError() != 0) throw _T("Error setting hinting.");
// Create shader
if (frame.format == FORMAT_YV12 && yv12shader == 0 && OpenGLWrapper::UseShaders()) {
yv12shader = OpenGLWrapper::CreateYV12Shader(texW,texH,float(frame.pitch[1])/float(tw));
}
// Set priority
float priority = 1.0f;
glPrioritizeTextures(1,&lastTex,&priority);
}
// Load texture data
glBindTexture(GL_TEXTURE_2D, lastTex);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,frame.pitch[0]/frame.GetBpp(0),frame.h,format,GL_UNSIGNED_BYTE,frame.data[0]);
if (glGetError() != 0) throw _T("Error uploading primary plane");
// UV planes for YV12
if (frame.format == FORMAT_YV12) {
int u = 1;
int v = 2;
if (frame.invertChannels) {
u = 2;
v = 1;
}
glTexSubImage2D(GL_TEXTURE_2D,0,0,frame.h,frame.pitch[1],frame.h/2,format,GL_UNSIGNED_BYTE,frame.data[u]);
if (glGetError() != 0) throw _T("Error uploading U plane.");
glTexSubImage2D(GL_TEXTURE_2D,0,frame.pitch[1],frame.h,frame.pitch[2],frame.h/2,format,GL_UNSIGNED_BYTE,frame.data[v]);
if (glGetError() != 0) throw _T("Error uploadinv V plane.");
}
// Return texture number
return lastTex;
}
/////////////////
// Save snapshot
/// @brief Save snapshot
/// @param raw
///
void VideoContext::SaveSnapshot(bool raw) {
// Get folder
wxString option = Options.AsText(_T("Video Screenshot Path"));
@ -918,7 +788,6 @@ wxThread::ExitCode VideoContextThread::Entry() {
// Get frame and set frame number
{
wxMutexLocker glLock(OpenGLWrapper::glMutex);
parent->GetFrameAsTexture(frame);
parent->frame_n = frame;
}

View File

@ -150,7 +150,6 @@ public:
void SaveSnapshot(bool raw);
wxGLContext *GetGLContext(wxGLCanvas *canvas);
GLuint GetFrameAsTexture(int n);
float GetTexW() { return texW; }
float GetTexH() { return texH; }
VideoFrameFormat GetFormat() { return vidFormat; }
@ -217,3 +216,4 @@ public:
VideoContextThread(VideoContext *parent);
wxThread::ExitCode Entry();
};

View File

@ -63,12 +63,12 @@
#include "vfw_wrap.h"
#include "mkv_wrap.h"
#include "options.h"
#include "subs_edit_box.h"
#include "audio_display.h"
#include "main.h"
#include "video_slider.h"
#include "utils.h"
#include "video_out_gl.h"
#include "vfr.h"
#include "video_box.h"
#include "gl_wrap.h"
#include "video_slider.h"
#include "visual_tool.h"
#include "visual_tool_cross.h"
#include "visual_tool_rotatez.h"
@ -119,6 +119,7 @@ VideoDisplay::VideoDisplay(wxWindow* parent, wxWindowID id, const wxPoint& pos,
#else
: wxGLCanvas (parent, id, attribList, pos, size, style, name)
#endif
, videoOut(new VideoOutGL())
{
// Set options
box = NULL;
@ -165,36 +166,77 @@ void VideoDisplay::ShowCursor(bool show) {
}
}
void VideoDisplay::SetFrame(int frameNumber) {
VideoContext *context = VideoContext::Get();
ControlSlider->SetValue(frameNumber);
// Get time for frame
int time = VFR_Output.GetTimeAtFrame(frameNumber, true, true);
int h = time / 3600000;
int m = time % 3600000 / 60000;
int s = time % 60000 / 1000;
int ms = time % 1000;
// Set the text box for frame number and time
PositionDisplay->SetValue(wxString::Format(_T("%01i:%02i:%02i.%03i - %i"), h, m, s, ms, frameNumber));
if (context->GetKeyFrames().Index(frameNumber) != wxNOT_FOUND) {
// Set the background color to indicate this is a keyframe
PositionDisplay->SetBackgroundColour(Options.AsColour(_T("Grid selection background")));
PositionDisplay->SetForegroundColour(Options.AsColour(_T("Grid selection foreground")));
}
else {
PositionDisplay->SetBackgroundColour(wxNullColour);
PositionDisplay->SetForegroundColour(wxNullColour);
}
wxString startSign;
wxString endSign;
int startOff = 0;
int endOff = 0;
if (AssDialogue *curLine = context->curLine) {
startOff = time - curLine->Start.GetMS();
endOff = time - curLine->End.GetMS();
}
// Positive signs
if (startOff > 0) startSign = _T("+");
if (endOff > 0) endSign = _T("+");
// Set the text box for time relative to active subtitle line
SubsPosition->SetValue(wxString::Format(_T("%s%ims; %s%ims"), startSign.c_str(), startOff, endSign.c_str(), endOff));
if (IsShownOnScreen() && visual) visual->Refresh();
// Render the new frame
if (context->IsLoaded()) {
AegiVideoFrame frame = context->GetFrame(frameNumber);
videoOut->UploadFrameData(frame);
}
Render();
currentFrame = frameNumber;
}
//////////
// Render
void VideoDisplay::Render()
// Yes it's legal C++ to replace the body of a function with one huge try..catch statement
try {
// Is shown?
void VideoDisplay::Render() try {
if (!IsShownOnScreen()) return;
if (!wxIsMainThread()) throw _T("Error: trying to render from non-primary thread");
// Get video context
VideoContext *context = VideoContext::Get();
wxASSERT(context);
if (!context->IsLoaded()) return;
// Set GL context
//wxMutexLocker glLock(OpenGLWrapper::glMutex);
#ifdef __WXMAC__
SetCurrent();
#else
SetCurrent(*context->GetGLContext(this));
#endif
// Get sizes
int w,h,sw,sh,pw,ph;
GetClientSize(&w,&h);
int w, h, sw, sh, pw, ph;
GetClientSize(&w, &h);
wxASSERT(w > 0);
wxASSERT(h > 0);
context->GetScriptSize(sw,sh);
context->GetScriptSize(sw, sh);
pw = context->GetWidth();
ph = context->GetHeight();
wxASSERT(pw > 0);
@ -216,9 +258,7 @@ try {
if (freeSize) {
// Set aspect ratio
float thisAr = float(w)/float(h);
float vidAr;
if (context->GetAspectRatioType() == 0) vidAr = float(pw)/float(ph);
else vidAr = context->GetAspectRatioValue();
float vidAr = context->GetAspectRatioType() == 0 ? float(pw)/float(ph) : context->GetAspectRatioValue();
// Window is wider than video, blackbox left/right
if (thisAr - vidAr > 0.01f) {
@ -236,8 +276,6 @@ try {
}
// Set viewport
glEnable(GL_TEXTURE_2D);
if (glGetError()) throw _T("Error enabling texturing.");
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(dx1,dy1,dx2,dy2);
@ -249,67 +287,34 @@ try {
if (glGetError()) throw _T("Error setting up matrices (wtf?).");
glShadeModel(GL_FLAT);
// Texture mode
if (w != pw || h != ph) {
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
if (glGetError()) throw _T("Error setting texture parameter min filter.");
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
if (glGetError()) throw _T("Error setting texture parameter mag filter.");
}
else {
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
if (glGetError()) throw _T("Error setting texture parameter min filter.");
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
if (glGetError()) throw _T("Error setting texture parameter mag filter.");
}
// Texture coordinates
float top = 0.0f;
float bot = context->GetTexH();
wxASSERT(bot != 0.0f);
if (context->IsInverted()) {
top = context->GetTexH();
bot = 0.0f;
}
float left = 0.0;
float right = context->GetTexW();
wxASSERT(right != 0.0f);
// Draw frame
glDisable(GL_BLEND);
if (glGetError()) throw _T("Error disabling blending.");
context->SetShader(true);
glBindTexture(GL_TEXTURE_2D, VideoContext::Get()->GetFrameAsTexture(-1));
if (glGetError()) throw _T("Error binding texture.");
glColor4f(1.0f,1.0f,1.0f,1.0f);
glBegin(GL_QUADS);
// Top-left
glTexCoord2f(left,top);
glVertex2f(0,0);
// Bottom-left
glTexCoord2f(left,bot);
glVertex2f(0,sh);
// Bottom-right
glTexCoord2f(right,bot);
glVertex2f(sw,sh);
// Top-right
glTexCoord2f(right,top);
glVertex2f(sw,0);
glEnd();
context->SetShader(false);
glDisable(GL_TEXTURE_2D);
// TV effects
videoOut->Render(sw, sh);
DrawTVEffects();
// Draw overlay
if (visual) visual->Draw();
visual->Draw();
// Swap buffers
glFinish();
//if (glGetError()) throw _T("Error finishing gl operation.");
SwapBuffers();
}
catch (const VideoOutUnsupportedException &err) {
wxLogError(
_T("An error occurred trying to render the video frame to screen.\n")
_T("Your graphics card does not appear to have a functioning OpenGL driver.\n")
_T("Error message reported: %s"),
err.GetMessage());
VideoContext::Get()->Reset();
}
catch (const VideoOutException &err) {
wxLogError(
_T("An error occurred trying to render the video frame to screen.\n")
_T("If you get this error regardless of which video file you use, and also if you use dummy video, Aegisub might not work with your graphics card's OpenGL driver.\n")
_T("Error message reported: %s"),
err.GetMessage());
VideoContext::Get()->Reset();
}
catch (const wxChar *err) {
wxLogError(
_T("An error occurred trying to render the video frame to screen.\n")
@ -552,82 +557,6 @@ void VideoDisplay::SetZoomPos(int value) {
if (zoomBox->GetSelection() != value) zoomBox->SetSelection(value);
}
////////////////////////////
// Updates position display
void VideoDisplay::UpdatePositionDisplay() {
// Update position display control
if (!PositionDisplay) {
throw _T("Position Display not set!");
}
// Get time
int frame_n = VideoContext::Get()->GetFrameN();
int time = VFR_Output.GetTimeAtFrame(frame_n,true,true);
int temp = time;
int h=0, m=0, s=0, ms=0;
while (temp >= 3600000) {
temp -= 3600000;
h++;
}
while (temp >= 60000) {
temp -= 60000;
m++;
}
while (temp >= 1000) {
temp -= 1000;
s++;
}
ms = temp;
// Position display update
PositionDisplay->SetValue(wxString::Format(_T("%01i:%02i:%02i.%03i - %i"),h,m,s,ms,frame_n));
if (VideoContext::Get()->GetKeyFrames().Index(frame_n) != wxNOT_FOUND) {
PositionDisplay->SetBackgroundColour(Options.AsColour(_T("Grid selection background")));
PositionDisplay->SetForegroundColour(Options.AsColour(_T("Grid selection foreground")));
}
else {
PositionDisplay->SetBackgroundColour(wxNullColour);
PositionDisplay->SetForegroundColour(wxNullColour);
}
// Subs position display update
UpdateSubsRelativeTime();
}
////////////////////////////////////////////////////
// Updates box with subs position relative to frame
void VideoDisplay::UpdateSubsRelativeTime() {
// Set variables
wxString startSign;
wxString endSign;
int startOff,endOff;
int frame_n = VideoContext::Get()->GetFrameN();
AssDialogue *curLine = VideoContext::Get()->curLine;
// Set start/end
if (curLine) {
int time = VFR_Output.GetTimeAtFrame(frame_n,true,true);
startOff = time - curLine->Start.GetMS();
endOff = time - curLine->End.GetMS();
}
// Fallback to zero
else {
startOff = 0;
endOff = 0;
}
// Positive signs
if (startOff > 0) startSign = _T("+");
if (endOff > 0) endSign = _T("+");
// Update line
SubsPosition->SetValue(wxString::Format(_T("%s%ims; %s%ims"),startSign.c_str(),startOff,endSign.c_str(),endOff));
}
/////////////////////
// Copy to clipboard
void VideoDisplay::OnCopyToClipboard(wxCommandEvent &event) {

View File

@ -46,6 +46,7 @@
#include <time.h>
#include <wx/glcanvas.h>
#include <wx/combobox.h>
#include <memory>
#include "video_context.h"
@ -59,6 +60,7 @@ class AssDialogue;
class VideoProvider;
class VisualTool;
class VideoBox;
class VideoOutGL;
//////////////
@ -74,6 +76,8 @@ private:
int w,h;
int dx1,dx2,dy1,dy2;
int mouse_x,mouse_y;
int currentFrame;
bool locked;
void DrawTVEffects();
@ -97,6 +101,7 @@ public:
double zoomValue;
bool freeSize;
std::auto_ptr<VideoOutGL> videoOut;
VideoSlider *ControlSlider;
wxComboBox *zoomBox;
@ -107,15 +112,16 @@ public:
~VideoDisplay();
void Reset();
void SetFrame(int frameNumber);
int GetFrame() const { return currentFrame; }
void Render();
void ShowCursor(bool show);
void ConvertMouseCoords(int &x,int &y);
void UpdatePositionDisplay();
void UpdateSize();
void SetZoom(double value);
void SetZoomPos(int pos);
void UpdateSubsRelativeTime();
void SetVisualMode(int mode);
DECLARE_EVENT_TABLE()

View File

@ -0,0 +1,326 @@
// Copyright (c) 2009, Thomas Goyne
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$
/// @file video_out_gl.cpp
/// @brief OpenGL based video renderer
/// @ingroup video
///
#include "config.h"
#ifndef AGI_PRE
#include <wx/log.h>
#endif
// These must be included before local headers.
#ifdef __APPLE__
#include <OpenGL/GL.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif
#include "video_out_gl.h"
#include "utils.h"
#include "video_frame.h"
// Windows only has headers for OpenGL 1.1 and GL_CLAMP_TO_EDGE is 1.2
#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE 0x812F
#endif
namespace {
/// @brief Structure tracking all precomputable information about a subtexture
struct TextureInfo {
/// The OpenGL texture id this is for
GLuint textureID;
/// The byte offset into the frame's data block
int dataOffset;
int sourceH;
int sourceW;
int textureH;
int textureW;
float destH;
float destW;
float destX;
float destY;
float texTop;
float texBottom;
float texLeft;
float texRight;
};
/// @brief Test if a texture can be created
/// @param width The width of the texture
/// @param height The height of the texture
/// @param format The texture's format
/// @return Whether the texture could be created.
bool TestTexture(int width, int height, GLint format) {
GLuint texture;
glGenTextures(1, &texture);
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
glDeleteTextures(1, &texture);
while (glGetError()) { } // Silently swallow all errors as we don't care why it failed if it did
wxLogDebug(L"VideoOutGL::TestTexture: %dx%d\n", width, height);
return format != 0;
}
}
VideoOutGL::VideoOutGL()
: maxTextureSize(0),
supportsRectangularTextures(false),
supportsGlClampToEdge(false),
internalFormat(0),
frameWidth(0),
frameHeight(0),
frameFormat(0),
frameFlipped(false),
textureIdList(),
textureList(),
textureCount(0),
textureRows(0),
textureCols(0)
{ }
/// @brief Runtime detection of required OpenGL capabilities
void VideoOutGL::DetectOpenGLCapabilities() {
if (maxTextureSize != 0) return;
// Test for supported internalformats
if (TestTexture(64, 64, GL_RGBA8)) internalFormat = GL_RGBA8;
else if (TestTexture(64, 64, GL_RGBA)) internalFormat = GL_RGBA;
else throw VideoOutUnsupportedException(L"Could not create a 64x64 RGB texture in any format.");
// Test for the maximum supported texture size
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
while (maxTextureSize > 64 && !TestTexture(maxTextureSize, maxTextureSize, internalFormat)) maxTextureSize >>= 1;
wxLogDebug(L"VideoOutGL::DetectOpenGLCapabilities: Maximum texture size is %dx%d\n", maxTextureSize, maxTextureSize);
// Test for rectangular texture support
supportsRectangularTextures = TestTexture(maxTextureSize, maxTextureSize >> 1, internalFormat);
// Test GL_CLAMP_TO_EDGE support
GLuint texture;
glGenTextures(1, &texture);
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
if (glGetError()) {
supportsGlClampToEdge = false;
wxLogDebug(L"VideoOutGL::DetectOpenGLCapabilities: Using GL_CLAMP\n");
}
else {
supportsGlClampToEdge = true;
wxLogDebug(L"VideoOutGL::DetectOpenGLCapabilities: Using GL_CLAMP_TO_EDGE\n");
}
}
/// @brief If needed, create the grid of textures for displaying frames of the given format
/// @param width The frame's width
/// @param height The frame's height
/// @param format The frame's format
/// @param bpp The frame's bytes per pixel
void VideoOutGL::InitTextures(int width, int height, GLenum format, int bpp, bool flipped) {
// Do nothing if the frame size and format are unchanged
if (width == frameWidth && height == frameHeight && format == frameFormat && frameFlipped == flipped) return;
wxLogDebug(L"VideoOutGL::InitTextures: Video size: %dx%d\n", width, height);
DetectOpenGLCapabilities();
// Clean up old textures
if (textureIdList.size() > 0) {
glDeleteTextures(textureIdList.size(), &textureIdList[0]);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glDeleteTextures", err);
textureIdList.clear();
textureList.clear();
}
// Create the textures
textureRows = (int)ceil(double(height) / maxTextureSize);
textureCols = (int)ceil(double(width) / maxTextureSize);
textureIdList.resize(textureRows * textureCols);
textureList.resize(textureRows * textureCols);
glGenTextures(textureIdList.size(), &textureIdList[0]);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glGenTextures", err);
// Calculate the position information for each texture
int sourceY = 0;
float destY = 0.0f;
for (int i = 0; i < textureRows; i++) {
int sourceX = 0;
float destX = 0.0f;
int sourceH = maxTextureSize;
int textureH = maxTextureSize;
// If the last row doesn't need a full texture, shrink it to the smallest one possible
if (i == textureRows - 1 && height % maxTextureSize > 0) {
sourceH = height % maxTextureSize;
textureH = SmallestPowerOf2(sourceH);
}
for (int j = 0; j < textureCols; j++) {
TextureInfo& ti = textureList[i * textureCols + j];
// Copy the current position information into the struct
ti.destX = destX;
ti.destY = destY;
ti.sourceH = sourceH;
ti.sourceW = maxTextureSize;
int textureW = maxTextureSize;
// If the last column doesn't need a full texture, shrink it to the smallest one possible
if (j == textureCols - 1 && width % maxTextureSize > 0) {
ti.sourceW = width % maxTextureSize;
textureW = SmallestPowerOf2(ti.sourceW);
}
int w = textureW;
int h = textureH;
if (!supportsRectangularTextures) w = h = MAX(w, h);
if (supportsGlClampToEdge) {
ti.texLeft = 0.0f;
ti.texTop = 0.0f;
}
else {
// Stretch the texture a half pixel in each direction to eliminate the border
ti.texLeft = 1.0f / (2 * w);
ti.texTop = 1.0f / (2 * h);
}
ti.destW = float(w) / width;
ti.destH = float(h) / height;
ti.textureID = textureIdList[i * textureCols + j];
ti.dataOffset = sourceY * width * bpp + sourceX * bpp;
ti.texRight = 1.0f - ti.texLeft;
ti.texBottom = 1.0f - ti.texTop;
if (flipped) {
ti.texBottom = ti.texTop - float(h - ti.sourceH) / h;
ti.texTop = 1.0f - ti.texTop - float(h - ti.sourceH) / h;
ti.dataOffset = (height - sourceY - ti.sourceH) * width * bpp + sourceX * bpp;
}
// Actually create the texture and set the scaling mode
glBindTexture(GL_TEXTURE_2D, ti.textureID);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glBindTexture", err);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, GL_UNSIGNED_BYTE, NULL);
wxLogDebug(L"VideoOutGL::InitTextures: Using texture size: %dx%d\n", w, h);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexImage2D", err);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_MIN_FILTER)", err);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_MAG_FILTER)", err);
GLint mode = supportsGlClampToEdge ? GL_CLAMP_TO_EDGE : GL_CLAMP;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_WRAP_S)", err);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_WRAP_T)", err);
destX += ti.destW;
sourceX += ti.sourceW;
}
destY += float(sourceH) / height;
sourceY += sourceH;
}
// Store the information needed to know when the grid must be recreated
frameWidth = width;
frameHeight = height;
frameFormat = format;
frameFlipped = flipped;
}
void VideoOutGL::UploadFrameData(const AegiVideoFrame& frame) {
if (frame.h == 0 || frame.w == 0) return;
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA;
InitTextures(frame.w, frame.h, format, frame.GetBpp(0), frame.flipped);
// Set the row length, needed to be able to upload partial rows
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.w);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glPixelStorei(GL_UNPACK_ROW_LENGTH, FrameWidth)", err);
for (unsigned i = 0; i < textureList.size(); i++) {
TextureInfo& ti = textureList[i];
glBindTexture(GL_TEXTURE_2D, ti.textureID);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glBindTexture", err);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW, ti.sourceH, format, GL_UNSIGNED_BYTE, frame.data[0] + ti.dataOffset);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexSubImage2D", err);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glPixelStorei(GL_UNPACK_ROW_LENGTH, default)", err);
}
void VideoOutGL::Render(int sw, int sh) {
glEnable(GL_TEXTURE_2D);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glEnable(GL_TEXTURE_2d)", err);
for (unsigned i = 0; i < textureList.size(); i++) {
TextureInfo& ti = textureList[i];
float destX = ti.destX * sw;
float destW = ti.destW * sw;
float destY = ti.destY * sh;
float destH = ti.destH * sh;
glBindTexture(GL_TEXTURE_2D, ti.textureID);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glBindTexture", err);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glColor4f", err);
glBegin(GL_QUADS);
glTexCoord2f(ti.texLeft, ti.texTop); glVertex2f(destX, destY);
glTexCoord2f(ti.texRight, ti.texTop); glVertex2f(destX + destW, destY);
glTexCoord2f(ti.texRight, ti.texBottom); glVertex2f(destX + destW, destY + destH);
glTexCoord2f(ti.texLeft, ti.texBottom); glVertex2f(destX, destY + destH);
glEnd();
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"GL_QUADS", err);
}
glDisable(GL_TEXTURE_2D);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glDisable(GL_TEXTURE_2d)", err);
}
VideoOutGL::~VideoOutGL() {
if (textureIdList.size() > 0) {
glDeleteTextures(textureIdList.size(), &textureIdList[0]);
}
}

121
aegisub/src/video_out_gl.h Normal file
View File

@ -0,0 +1,121 @@
// Copyright (c) 2009, Thomas Goyne
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id: video_out_gl.h 3613 2009-10-05 00:06:11Z plorkyeran $
/// @file video_out_gl.h
/// @brief OpenGL based video renderer
/// @ingroup video
///
#include "include/aegisub/exception.h"
#include <vector>
class AegiVideoFrame;
namespace {
struct TextureInfo;
}
/// @class VideoOutGL
/// @brief OpenGL based video renderer
class VideoOutGL {
private:
/// The maximum texture size supported by the user's graphics card
int maxTextureSize;
/// Whether rectangular textures are supported by the user's graphics card
bool supportsRectangularTextures;
/// Whether GL_CLAMP_TO_EDGE is supported by the user's drivers
bool supportsGlClampToEdge;
/// The internalformat to use
int internalFormat;
/// The frame height which the texture grid has been set up for
int frameWidth;
/// The frame width which the texture grid has been set up for
int frameHeight;
/// The frame format which the texture grid has been set up for
GLenum frameFormat;
/// Whether the grid is set up for flipped video
bool frameFlipped;
/// List of OpenGL texture ids used in the grid
std::vector<GLuint> textureIdList;
/// List of precalculated texture display information
std::vector<TextureInfo> textureList;
/// The total texture count
int textureCount;
/// The number of rows of textures
int textureRows;
/// The number of columns of textures
int textureCols;
void DetectOpenGLCapabilities();
void InitTextures(int width, int height, GLenum format, int bpp, bool flipped);
VideoOutGL(const VideoOutGL &);
VideoOutGL& operator=(const VideoOutGL&);
public:
/// @brief Set the frame to be displayed when Render() is called
/// @param frame The frame to be displayed
void UploadFrameData(const AegiVideoFrame& frame);
/// @brief Render a frame
/// @param sw The current script width
/// @param sh The current script height
void Render(int sw, int sh);
/// @brief Constructor
VideoOutGL();
/// @brief Destructor
~VideoOutGL();
};
/// @class VideoOutException
/// @extends Aegisub::Exception
/// @brief Base class for all exceptions thrown by VideoOutGL
DEFINE_BASE_EXCEPTION_NOINNER(VideoOutException, Aegisub::Exception)
/// @class VideoOutUnsupportedException
/// @extends VideoOutException
/// @brief The user's video card does not support OpenGL to any usable extent
DEFINE_SIMPLE_EXCEPTION_NOINNER(VideoOutUnsupportedException, VideoOutException, "videoout/unsupported")
/// @class VideoOutOpenGLException
/// @extends VideoOutException
/// @brief An OpenGL error occured.
///
/// Unlike VideoOutUnsupportedException, these errors are likely to be video-specific
/// and/or due to an Aegisub bug.
class VideoOutOpenGLException : public VideoOutException {
public:
VideoOutOpenGLException(const wxChar *func, int err)
: VideoOutException(wxString::Format(L"%s failed with error code %d", func, err))
{ }
const wxChar * GetName() const { return L"videoout/opengl"; }
Exception * Copy() const { return new VideoOutOpenGLException(*this); }
};