Rewrite the video rendering code to support displaying videos which are larger than the maximum texture size. This does not currently support YV12 (which Aegisub currently never uses), but should be otherwise functional.

Originally committed to SVN as r3615.
This commit is contained in:
Thomas Goyne 2009-10-05 04:22:28 +00:00
parent 39954493c0
commit cbd76e7010
11 changed files with 525 additions and 313 deletions

View File

@ -1835,6 +1835,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

@ -305,6 +305,7 @@ aegisub_2_2_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

@ -1082,7 +1082,7 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
wxString ar = _T("0");
wxString zoom = _T("6");
if (VideoContext::Get()->IsLoaded()) {
seekpos = wxString::Format(_T("%i"),videoBox->videoDisplay->ControlSlider->GetValue());
seekpos = wxString::Format(_T("%i"),videoBox->videoDisplay->GetFrame());
zoom = wxString::Format(_T("%i"),videoBox->videoDisplay->zoomBox->GetSelection()+1);
int arType = VideoContext::Get()->GetAspectRatioType();

View File

@ -121,10 +121,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached)
visualToolBar->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
// Display
videoDisplay = new VideoDisplay(videoPage,-1,this,wxDefaultPosition,wxDefaultSize,wxSUNKEN_BORDER);
videoDisplay->ControlSlider = videoSlider;
videoDisplay->PositionDisplay = VideoPosition;
videoDisplay->SubsPosition = VideoSubsPos;
videoDisplay = new VideoDisplay(this,videoSlider,VideoPosition,VideoSubsPos,videoPage,-1,wxDefaultPosition,wxDefaultSize,wxSUNKEN_BORDER);
VideoContext::Get()->AddDisplay(videoDisplay);
videoDisplay->Reset();
@ -244,7 +241,7 @@ void VideoBox::OnModeChange(wxCommandEvent &event) {
/// @param event
///
void VideoBox::OnSubTool(wxCommandEvent &event) {
videoDisplay->visual->OnSubTool(event);
videoDisplay->OnSubTool(event);
}

View File

@ -73,7 +73,6 @@
#include "video_context.h"
#include "video_display.h"
#include "video_provider_manager.h"
#include "video_slider.h"
#include "visual_tool.h"
@ -170,10 +169,8 @@ void VideoContext::Clear() {
/// @brief Reset
///
void VideoContext::Reset() {
// Reset ?video path
StandardPaths::SetPathValue(_T("?video"),_T(""));
// Clear keyframes
KeyFrames.Clear();
keyFramesLoaded = false;
@ -357,28 +354,13 @@ void VideoContext::RemoveDisplay(VideoDisplay *display) {
///
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->SetFrameRange(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
@ -389,8 +371,6 @@ void VideoContext::UpdateDisplays(bool full) {
}
}
/// @brief Refresh subtitles
/// @param video
/// @param subtitles
@ -445,7 +425,6 @@ void VideoContext::JumpToFrame(int n) {
try {
// Set frame number
frame_n = n;
GetFrameAsTexture(n);
// Display
UpdateDisplays(false);
@ -523,91 +502,6 @@ AegiVideoFrame VideoContext::GetFrame(int n,bool raw) {
else return frame;
}
/// @brief Get GL Texture of frame
/// @param n
/// @return
///
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
GetGLContext(displayList.front())->SetCurrent(*displayList.front());
glEnable(GL_TEXTURE_2D);
if (glGetError() != 0) throw _T("Error enabling texture.");
// Image type
GLenum format;
if (frame.invertChannels) format = GL_BGRA_EXT;
else format = GL_RGBA;
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;
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.");
// 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");
// Return texture number
return lastTex;
}
/// @brief Save snapshot
/// @param raw
///
@ -913,7 +807,6 @@ wxThread::ExitCode VideoContextThread::Entry() {
// Get frame and set frame number
{
wxMutexLocker glLock(OpenGLWrapper::glMutex);
parent->GetFrameAsTexture(frame);
parent->frame_n = frame;
}

View File

@ -91,14 +91,12 @@ class VideoContext : public wxEvtHandler {
friend class VideoContextThread;
private:
/// DOCME
static VideoContext *instance;
/// DOCME
std::list<VideoDisplay*> displayList;
/// DOCME
GLuint lastTex;
@ -255,89 +253,71 @@ public:
void SaveSnapshot(bool raw);
wxGLContext *GetGLContext(wxGLCanvas *canvas);
GLuint GetFrameAsTexture(int n);
/// @brief DOCME
/// @return
///
float GetTexW() { return texW; }
/// @brief DOCME
/// @return
///
float GetTexH() { return texH; }
/// @brief DOCME
/// @return
///
VideoFrameFormat GetFormat() { return vidFormat; }
/// @brief DOCME
/// @return
///
bool IsLoaded() { return loaded; }
/// @brief DOCME
/// @return
///
bool IsPlaying() { return isPlaying; }
/// @brief DOCME
/// @return
///
bool IsInverted() { return isInverted; }
/// @brief DOCME
/// @param sync
/// @return
///
void EnableAudioSync(bool sync = true) { keepAudioSync = sync; }
/// @brief DOCME
/// @return
///
int GetWidth() { return w; }
/// @brief DOCME
/// @return
///
int GetHeight() { return h; }
/// @brief DOCME
/// @return
///
int GetLength() { return length; }
/// @brief DOCME
/// @return
///
int GetFrameN() { return frame_n; }
/// @brief DOCME
/// @return
///
double GetFPS() { return fps; }
/// @brief DOCME
/// @param _fps
/// @return
///
void SetFPS(double _fps) { fps = _fps; }
void SetFPS(double fps) { this->fps = fps; }
double GetARFromType(int type);
void SetAspectRatio(int type,double value=1.0);
/// @brief DOCME
/// @return
///
int GetAspectRatioType() { return arType; }
/// @brief DOCME
/// @return
///
double GetAspectRatioValue() { return arValue; }
void SetVideo(const wxString &filename);

View File

@ -57,11 +57,13 @@
#include "hotkeys.h"
#include "options.h"
#include "utils.h"
#include "video_out_gl.h"
#include "vfr.h"
#include "video_box.h"
#include "video_context.h"
#include "video_display.h"
#include "video_provider_manager.h"
#include "video_slider.h"
#include "visual_tool.h"
#include "visual_tool_clip.h"
#include "visual_tool_cross.h"
@ -114,32 +116,35 @@ int attribList[] = { WX_GL_RGBA , WX_GL_DOUBLEBUFFER, WX_GL_STENCIL_SIZE, 8, 0 }
/// @param size Window size. wxDefaultSize is (-1, -1) which indicates that wxWidgets should generate a default size for the window. If no suitable size can be found, the window will be sized to 20x20 pixels so that the window is visible but obviously not correctly sized.
/// @param style Window style.
/// @param name Window name.
VideoDisplay::VideoDisplay(wxWindow* parent, wxWindowID id, VideoBox *box, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
: wxGLCanvas (parent, id, attribList, pos, size, style, name), box(box)
VideoDisplay::VideoDisplay(VideoBox *box, VideoSlider *ControlSlider, wxTextCtrl *PositionDisplay, wxTextCtrl *SubsPosition, wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
: wxGLCanvas (parent, id, attribList, pos, size, style, name)
, visualMode(-1)
, origSize(size)
, currentFrame(-1)
, w(8), h(8), dx1(0), dx2(8), dy1(0), dy2(8)
, mouse_x(-1), mouse_y(-1)
, locked(false)
, zoomValue(1.0)
, ControlSlider(ControlSlider)
, SubsPosition(SubsPosition)
, PositionDisplay(PositionDisplay)
, visual(NULL)
, box(box)
, freeSize(false)
{
locked = false;
ControlSlider = NULL;
PositionDisplay = NULL;
w=h=dx2=dy2=8;
dx1=dy1=0;
origSize = size;
zoomValue = 1.0;
freeSize = false;
visual = NULL;
videoOut = new VideoOutGL();
SetCursor(wxNullCursor);
visualMode = -1;
SetVisualMode(0);
}
/// @brief Destructor
VideoDisplay::~VideoDisplay () {
delete visual;
if (visual) delete visual;
visual = NULL;
VideoContext::Get()->RemoveDisplay(this);
}
/// @brief Set the cursor to either default or blank
/// @param Whether or not the cursor should be visible
/// @param show Whether or not the cursor should be visible
void VideoDisplay::ShowCursor(bool show) {
if (show) SetCursor(wxNullCursor);
else {
@ -148,30 +153,79 @@ void VideoDisplay::ShowCursor(bool show) {
}
}
/// @brief Render the currently visible frame
void VideoDisplay::Render()
// Yes it's legal C++ to replace the body of a function with one huge try..catch statement
try {
void VideoDisplay::SetFrame(int frameNumber) {
ControlSlider->SetValue(frameNumber);
// Is shown?
// 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 (VideoContext::Get()->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 = VideoContext::Get()->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()) {
// Update the visual typesetting tools
if (visual) visual->Refresh();
// Render the new frame
Render(frameNumber);
}
currentFrame = frameNumber;
}
void VideoDisplay::SetFrameRange(int from, int to) {
ControlSlider->SetRange(from, to);
}
/// @brief Render the currently visible frame
void VideoDisplay::Render(int frameNumber) 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);
SetCurrent(*context->GetGLContext(this));
// 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);
@ -193,9 +247,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) {
@ -213,8 +265,6 @@ try {
}
// Set viewport
glEnable(GL_TEXTURE_2D);
if (glGetError()) throw _T("Error enabling texturing.");
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(dx1,dy1,dx2,dy2);
@ -226,65 +276,35 @@ 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.");
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();
glDisable(GL_TEXTURE_2D);
// TV effects
videoOut->DisplayFrame(context->GetFrame(frameNumber), sw, sh);
DrawTVEffects();
// Draw overlay
if (visualMode == -1) SetVisualMode(0, false);
if (visual) 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")
@ -516,78 +536,6 @@ void VideoDisplay::SetZoomPos(int value) {
if (zoomBox->GetSelection() != value) zoomBox->SetSelection(value);
}
/// @brief Update the absolute frame time 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();
}
/// @brief Update the relative-to-subs time display
void VideoDisplay::UpdateSubsRelativeTime() {
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));
}
/// @brief Copy the currently display frame to the clipboard, with subtitles
/// @param event Unused
void VideoDisplay::OnCopyToClipboard(wxCommandEvent &event) {
@ -659,7 +607,8 @@ void VideoDisplay::ConvertMouseCoords(int &x,int &y) {
/// @brief Set the current visual typesetting mode
/// @param mode The new mode
void VideoDisplay::SetVisualMode(int mode) {
/// @param render Whether the display should be rerendered
void VideoDisplay::SetVisualMode(int mode, bool render) {
// Set visual
if (visualMode != mode) {
wxToolBar *toolBar = NULL;
@ -690,5 +639,9 @@ void VideoDisplay::SetVisualMode(int mode) {
// Update size as the new typesetting tool may have changed the subtoolbar size
UpdateSize();
}
Render();
if (render) Render();
}
void VideoDisplay::OnSubTool(wxCommandEvent &event) {
if (visual) visual->OnSubTool(event);
}

View File

@ -46,6 +46,7 @@
class VideoSlider;
class VisualTool;
class VideoBox;
class VideoOutGL;
/// DOCME
/// @class VideoDisplay
@ -60,6 +61,9 @@ private:
/// The unscaled size of the displayed video
wxSize origSize;
/// The frame number currently being displayed
int currentFrame;
/// The width of the display
int w;
/// The height of the display
@ -107,42 +111,49 @@ private:
/// The current zoom level, where 1.0 = 100%
double zoomValue;
/// The VideoBox this display is contained in
VideoBox *box;
public:
/// The current visual typesetting tool
VisualTool *visual;
/// Whether the display can be freely resized by the user
bool freeSize;
/// The video position slider; not used by VideoDisplay
/// The video position slider
VideoSlider *ControlSlider;
/// The dropdown box for selecting zoom levels
wxComboBox *zoomBox;
/// The display for the absolute time of the video position
wxTextCtrl *PositionDisplay;
/// The display for the the video position relative to the current subtitle line
wxTextCtrl *SubsPosition;
VideoDisplay(wxWindow* parent, wxWindowID id, VideoBox *box, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxPanelNameStr);
/// The display for the absolute time of the video position
wxTextCtrl *PositionDisplay;
/// The current visual typesetting tool
VisualTool *visual;
/// The video renderer
VideoOutGL *videoOut;
public:
/// The VideoBox this display is contained in
VideoBox *box;
/// The dropdown box for selecting zoom levels
wxComboBox *zoomBox;
/// Whether the display can be freely resized by the user
bool freeSize;
VideoDisplay(VideoBox *box, VideoSlider *ControlSlider, wxTextCtrl *PositionDisplay, wxTextCtrl *SubsPosition, wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxPanelNameStr);
~VideoDisplay();
void Reset();
void Render();
void SetFrame(int frameNumber);
int GetFrame() const { return currentFrame; }
void SetFrameRange(int from, int to);
void Render(int frameNumber = -1);
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);
void SetVisualMode(int mode, bool render = false);
void OnSubTool(wxCommandEvent &event);
DECLARE_EVENT_TABLE()
};

View File

@ -269,10 +269,9 @@ void AegiVideoFrame::GetFloat(float *buffer) const {
/// @brief Get Bytes per Pixel
/// @brief Get bytes per pixel for the current frame format
/// @param plane
/// @return
///
int AegiVideoFrame::GetBpp(int plane) const {
switch (format) {
case FORMAT_RGB32: return 4;

View File

@ -0,0 +1,259 @@
// 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.cpp 3613 2009-10-05 00:06:11Z plorkyeran $
/// @file video_out_gl.cpp
/// @brief OpenGL based video renderer
/// @ingroup video
///
#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"
#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;
float destH;
float destW;
float destX;
float destY;
float texH;
float texW;
};
/// @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
return format != 0;
}
}
VideoOutGL::VideoOutGL()
: maxTextureSize(0),
supportsRectangularTextures(false),
internalFormat(0),
frameWidth(0),
frameHeight(0),
frameFormat(0),
textureIdList(NULL),
textureList(NULL),
textureCount(0),
textureRows(0),
textureCols(0)
{ }
/// @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) {
// Do nothing if the frame size and format are unchanged
if (width == frameWidth && height == frameHeight && format == frameFormat) return;
frameWidth = width;
frameHeight = height;
frameFormat = format;
// If nessesary, detect what the user's OpenGL supports
if (maxTextureSize == 0) {
// 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;
// Test for rectangular texture support
supportsRectangularTextures = TestTexture(maxTextureSize, maxTextureSize >> 1, internalFormat);
}
// Clean up old textures
if (textureList != NULL) {
glDeleteTextures(textureCount, textureIdList);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glDeleteTextures", err);
delete textureIdList;
delete textureList;
textureCount = 0;
textureList = NULL;
}
textureRows = (int)ceil(double(height) / maxTextureSize);
textureCols = (int)ceil(double(width) / maxTextureSize);
textureCount = textureRows * textureCols;
textureIdList = static_cast<GLuint *>(calloc(textureCount, sizeof(GLint)));
glGenTextures(textureCount, textureIdList);
textureList = new TextureInfo[textureCount];
// 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;
// If the last row doesn't need a full texture, shrink it to the smallest one possible
if (i == textureRows - 1 && frameHeight % maxTextureSize > 0) {
sourceH = frameHeight % maxTextureSize;
}
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;
// If the last column doesn't need a full texture, shrink it to the smallest one possible
if (j == textureCols - 1 && frameWidth % maxTextureSize > 0) {
ti.sourceW = frameWidth % maxTextureSize;
}
int w = SmallestPowerOf2(ti.sourceW);
int h = SmallestPowerOf2(ti.sourceH);
if (!supportsRectangularTextures) w = h = MAX(w, h);
// Calculate what percent of the texture is actually used
ti.texW = float(ti.sourceW) / w;
ti.texH = float(ti.sourceH) / h;
// destW/H is the percent of the output which this texture covers
ti.destW = float(ti.sourceW) / frameWidth;
ti.destH = float(ti.sourceH) / frameHeight;
ti.textureID = textureIdList[i * textureCols + j];
ti.dataOffset = sourceY * frameWidth * 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);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_WRAP_S)", err);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glTexParameteri(GL_TEXTURE_WRAP_T)", err);
destX += ti.destW;
sourceX += ti.sourceW;
}
destY += float(sourceH) / frameHeight;
sourceY += sourceH;
}
}
void VideoOutGL::DisplayFrame(AegiVideoFrame frame, int sw, int sh) {
if (frame.h == 0 || frame.w == 0) return;
glEnable(GL_TEXTURE_2D);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glEnable(GL_TEXTURE_2d)", err);
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA;
InitTextures(frame.w, frame.h, format, frame.GetBpp(0));
// 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 (int i = 0; i < textureCount; 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);
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);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glColor4f", err);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0); glVertex2f(destX, destY);
glTexCoord2f(ti.texW, 0.0); glVertex2f(destX + destW, destY);
glTexCoord2f(ti.texW, ti.texH); glVertex2f(destX + destW, destY + destH);
glTexCoord2f(0.0, ti.texH); glVertex2f(destX, destY + destH);
glEnd();
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"GL_QUADS", err);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glPixelStorei(GL_UNPACK_ROW_LENGTH, default)", err);
glDisable(GL_TEXTURE_2D);
if (GLenum err = glGetError()) throw VideoOutOpenGLException(L"glDisable(GL_TEXTURE_2d)", err);
}
VideoOutGL::~VideoOutGL() {
if (textureList != NULL) {
glDeleteTextures(textureCount, textureIdList);
delete textureIdList;
delete textureList;
textureCount = 0;
textureList = NULL;
}
}

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

@ -0,0 +1,111 @@
// 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"
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;
/// 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;
/// List of OpenGL texture ids used in the grid
GLuint *textureIdList;
/// List of precalculated texture display information
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 InitTextures(int width, int height, GLenum format, int bpp);
VideoOutGL(const VideoOutGL &);
VideoOutGL& operator=(const VideoOutGL&);
public:
/// @brief Render a frame
/// @param frame The frame to be displayed
/// @param sw The current script width
/// @param sh The current script height
void DisplayFrame(AegiVideoFrame frame, 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("%s failed with error code %d", func, err))
{ }
const wxChar * GetName() const { return L"videoout/opengl"; }
Exception * Copy() const { return new VideoOutOpenGLException(*this); }
};