From cbd76e70103a5a859e8f98e640c019a797d6c88c Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 5 Oct 2009 04:22:28 +0000 Subject: [PATCH] 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. --- .../aegisub_vs2008/aegisub_vs2008.vcproj | 8 + aegisub/src/Makefile.am | 1 + aegisub/src/frame_main.cpp | 2 +- aegisub/src/video_box.cpp | 7 +- aegisub/src/video_context.cpp | 111 +------- aegisub/src/video_context.h | 22 +- aegisub/src/video_display.cpp | 259 +++++++----------- aegisub/src/video_display.h | 55 ++-- aegisub/src/video_frame.cpp | 3 +- aegisub/src/video_out_gl.cpp | 259 ++++++++++++++++++ aegisub/src/video_out_gl.h | 111 ++++++++ 11 files changed, 525 insertions(+), 313 deletions(-) create mode 100644 aegisub/src/video_out_gl.cpp create mode 100644 aegisub/src/video_out_gl.h diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj index 2d371f1e8..68451a990 100644 --- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj +++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj @@ -1835,6 +1835,14 @@ RelativePath="..\..\src\video_display.h" > + + + + diff --git a/aegisub/src/Makefile.am b/aegisub/src/Makefile.am index 600725aa1..feec1f77e 100644 --- a/aegisub/src/Makefile.am +++ b/aegisub/src/Makefile.am @@ -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 \ diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp index 73819929c..44a44bc74 100644 --- a/aegisub/src/frame_main.cpp +++ b/aegisub/src/frame_main.cpp @@ -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(); diff --git a/aegisub/src/video_box.cpp b/aegisub/src/video_box.cpp index 95cbc6c32..911e880e2 100644 --- a/aegisub/src/video_box.cpp +++ b/aegisub/src/video_box.cpp @@ -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); } diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp index ae2e5de03..83fae97a5 100644 --- a/aegisub/src/video_context.cpp +++ b/aegisub/src/video_context.cpp @@ -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::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; } diff --git a/aegisub/src/video_context.h b/aegisub/src/video_context.h index a99992ffc..ca6e09e29 100644 --- a/aegisub/src/video_context.h +++ b/aegisub/src/video_context.h @@ -91,14 +91,12 @@ class VideoContext : public wxEvtHandler { friend class VideoContextThread; private: - /// DOCME static VideoContext *instance; /// DOCME std::list 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); diff --git a/aegisub/src/video_display.cpp b/aegisub/src/video_display.cpp index 6729d9353..3848640d3 100644 --- a/aegisub/src/video_display.cpp +++ b/aegisub/src/video_display.cpp @@ -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); } diff --git a/aegisub/src/video_display.h b/aegisub/src/video_display.h index 5db29dfff..c6672da1a 100644 --- a/aegisub/src/video_display.h +++ b/aegisub/src/video_display.h @@ -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() }; diff --git a/aegisub/src/video_frame.cpp b/aegisub/src/video_frame.cpp index 4ee9aeb75..6d9b0430f 100644 --- a/aegisub/src/video_frame.cpp +++ b/aegisub/src/video_frame.cpp @@ -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; diff --git a/aegisub/src/video_out_gl.cpp b/aegisub/src/video_out_gl.cpp new file mode 100644 index 000000000..cc811a5ef --- /dev/null +++ b/aegisub/src/video_out_gl.cpp @@ -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 +#include +#else +#include +#include +#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(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; + } +} diff --git a/aegisub/src/video_out_gl.h b/aegisub/src/video_out_gl.h new file mode 100644 index 000000000..d180fc90a --- /dev/null +++ b/aegisub/src/video_out_gl.h @@ -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); } +};