diff options
author | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2010-10-10 22:59:33 +0000 |
---|---|---|
committer | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2010-10-10 22:59:33 +0000 |
commit | 35187604cef84864a908972d07361a5bd57e29ca (patch) | |
tree | dc95a8b1abeabd3a466729056ab8d37aaa6e72ea /src/media/UVisualizer.pas | |
parent | 58c1daf3692d4c5c534750a4fda97e087b0f0cbb (diff) | |
parent | 02bd10f0798829ab69d2028b988cb2a54eae292a (diff) | |
download | usdx-svn/1.1.tar.gz usdx-svn/1.1.tar.xz usdx-svn/1.1.zip |
rename trunk to 1.1svn/1.1github/svn/1.1
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/1.1@2662 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'src/media/UVisualizer.pas')
-rw-r--r-- | src/media/UVisualizer.pas | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas new file mode 100644 index 00000000..1cdc3500 --- /dev/null +++ b/src/media/UVisualizer.pas @@ -0,0 +1,685 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + *} + +unit UVisualizer; + +(* TODO: + * - fix video/visualizer switching + * - use GL_EXT_framebuffer_object for rendering to a separate framebuffer, + * this will prevent plugins from messing up our render-context + * (-> no stack corruption anymore, no need for Save/RestoreOpenGLState()). + * - create a generic (C-compatible) interface for visualization plugins + * - create a visualization plugin manager + * - write a plugin for projectM in C/C++ (so we need no wrapper anymore) + *) + +{* Note: + * It would be easier to create a seperate Render-Context (RC) for projectM + * and switch to it when necessary. This can be achieved by pbuffers + * (slow and platform specific) or the OpenGL FramebufferObject (FBO) extension + * (fast and plattform-independent but not supported by older graphic-cards/drivers). + * + * See http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt + * + * To support as many cards as possible we will stick to the current dirty + * solution for now even if it is a pain to save/restore projectM's state due + * to bugs etc. + * + * This also restricts us to projectM. As other plug-ins might have different + * needs and bugs concerning the OpenGL state, USDX's state would probably be + * corrupted after the plug-in finshed drawing. + *} + +interface + +{$IFDEF FPC} + {$MODE DELPHI} +{$ENDIF} + +{$I switches.inc} + +{.$DEFINE UseTexture} + +uses + SDL, + UGraphicClasses, + textgl, + math, + gl, + {$IFDEF UseTexture} + glu, + {$ENDIF} + SysUtils, + UIni, + projectM, + UMusic; + +implementation + +uses + UGraphic, + UMain, + UConfig, + UPath, + ULog; + +{$IF PROJECTM_VERSION < 1000000} // < 1.0 +// Initialization data used on projectM 0.9x creation. +// Since projectM 1.0 this data is passed via the config-file. +const + meshX = 32; + meshY = 24; + fps = 30; + textureSize = 512; +{$IFEND} + +type + TProjectMState = ( pmPlay, pmStop, pmPause ); + +type + TGLMatrix = array[0..3, 0..3] of GLdouble; + TGLMatrixStack = array of TGLMatrix; + +type + TVideo_ProjectM = class( TInterfacedObject, IVideo ) + private + fPm: TProjectM; + fProjectMPath : string; + + fState: TProjectMState; + + fScreen: integer; + + fVisualTex: GLuint; + fPCMData: TPCMData; + fRndPCMcount: integer; + + fModelviewMatrixStack: TGLMatrixStack; + fProjectionMatrixStack: TGLMatrixStack; + fTextureMatrixStack: TGLMatrixStack; + + procedure InitProjectM; + + function GetRandomPCMData(var Data: TPCMData): Cardinal; + + function GetMatrixStackDepth(MatrixMode: GLenum): GLint; + procedure SaveMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack); + procedure RestoreMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack); + procedure SaveOpenGLState(); + procedure RestoreOpenGLState(); + + public + constructor Create; + destructor Destroy; override; + + procedure Close; + + procedure Play; + procedure Pause; + procedure Stop; + + procedure SetPosition(Time: real); + function GetPosition: real; + + procedure SetLoop(Enable: boolean); + function GetLoop(): boolean; + + procedure SetScreen(Screen: integer); + function GetScreen(): integer; + + procedure SetScreenPosition(X, Y, Z: double); + procedure GetScreenPosition(var X, Y, Z: double); + + procedure SetWidth(Width: double); + function GetWidth(): double; + + procedure SetHeight(Height: double); + function GetHeight(): double; + + procedure SetFrameRange(Range: TRectCoords); + function GetFrameRange(): TRectCoords; + + function GetFrameAspect(): real; + + procedure SetAspectCorrection(AspectCorrection: TAspectCorrection); + function GetAspectCorrection(): TAspectCorrection; + + procedure SetAlpha(Alpha: double); + function GetAlpha(): double; + + procedure SetReflectionSpacing(Spacing: double); + function GetReflectionSpacing(): double; + + procedure GetFrame(Time: Extended); + procedure Draw(); + procedure DrawReflection(); + end; + + TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoVisualization ) + private + fInitialized: boolean; + + public + function GetName: String; + + function Init(): boolean; + function Finalize(): boolean; + + function Open(const aFileName: IPath): IVideo; + end; + + +{ TVideoPlayback_ProjectM } + +function TVideoPlayback_ProjectM.GetName: String; +begin + Result := 'ProjectM'; +end; + +function TVideoPlayback_ProjectM.Init(): boolean; +begin + Result := true; + if (fInitialized) then + Exit; + fInitialized := true; +end; + +function TVideoPlayback_ProjectM.Finalize(): boolean; +begin + Result := true; +end; + +function TVideoPlayback_ProjectM.Open(const aFileName: IPath): IVideo; +begin + Result := TVideo_ProjectM.Create; +end; + + +{ TVideo_ProjectM } + +constructor TVideo_ProjectM.Create; +begin + fRndPCMcount := 0; + + fProjectMPath := ProjectM_DataDir + PathDelim; + + fState := pmStop; + + {$IFDEF UseTexture} + glGenTextures(1, PglUint(@fVisualTex)); + glBindTexture(GL_TEXTURE_2D, fVisualTex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + {$ENDIF} + + InitProjectM(); +end; + +destructor TVideo_ProjectM.Destroy; +begin + Close(); + {$IFDEF UseTexture} + glDeleteTextures(1, PglUint(@fVisualTex)); + {$ENDIF} +end; + +procedure TVideo_ProjectM.Close; +begin + FreeAndNil(fPm); +end; + +procedure TVideo_ProjectM.Play; +begin + if (fState = pmStop) and (assigned(fPm)) then + fPm.RandomPreset(); + fState := pmPlay; +end; + +procedure TVideo_ProjectM.Pause; +begin + if (fState = pmPlay) then + fState := pmPause + else if (fState = pmPause) then + fState := pmPlay; +end; + +procedure TVideo_ProjectM.Stop; +begin + fState := pmStop; +end; + +procedure TVideo_ProjectM.SetPosition(Time: real); +begin + if assigned(fPm) then + fPm.RandomPreset(); +end; + +function TVideo_ProjectM.GetPosition: real; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetLoop(Enable: boolean); +begin +end; + +function TVideo_ProjectM.GetLoop(): boolean; +begin + Result := true; +end; + +procedure TVideo_ProjectM.SetScreen(Screen: integer); +begin +end; + +function TVideo_ProjectM.GetScreen(): integer; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetScreenPosition(X, Y, Z: double); +begin +end; + +procedure TVideo_ProjectM.GetScreenPosition(var X, Y, Z: double); +begin + X := 0; + Y := 0; + Z := 0; +end; + +procedure TVideo_ProjectM.SetWidth(Width: double); +begin +end; + +function TVideo_ProjectM.GetWidth(): double; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetHeight(Height: double); +begin +end; + +function TVideo_ProjectM.GetHeight(): double; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetFrameRange(Range: TRectCoords); +begin +end; + +function TVideo_ProjectM.GetFrameRange(): TRectCoords; +begin + Result.Left := 0; + Result.Right := 0; + Result.Upper := 0; + Result.Lower := 0; +end; + +function TVideo_ProjectM.GetFrameAspect(): real; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetAspectCorrection(AspectCorrection: TAspectCorrection); +begin +end; + +function TVideo_ProjectM.GetAspectCorrection(): TAspectCorrection; +begin + Result := acoStretch; +end; + +procedure TVideo_ProjectM.SetAlpha(Alpha: double); +begin +end; + +function TVideo_ProjectM.GetAlpha(): double; +begin + Result := 1; +end; + +procedure TVideo_ProjectM.SetReflectionSpacing(Spacing: double); +begin +end; + +function TVideo_ProjectM.GetReflectionSpacing(): double; +begin + Result := 0; +end; + +{** + * Returns the stack depth of the given OpenGL matrix mode stack. + *} +function TVideo_ProjectM.GetMatrixStackDepth(MatrixMode: GLenum): GLint; +begin + // get number of matrices on stack + case (MatrixMode) of + GL_PROJECTION: + glGetIntegerv(GL_PROJECTION_STACK_DEPTH, @Result); + GL_MODELVIEW: + glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, @Result); + GL_TEXTURE: + glGetIntegerv(GL_TEXTURE_STACK_DEPTH, @Result); + end; +end; + +{** + * Saves the current matrix stack using MatrixMode + * (one of GL_PROJECTION/GL_TEXTURE/GL_MODELVIEW) + * + * Use this function instead of just saving the current matrix with glPushMatrix(). + * OpenGL specifies the depth of the GL_PROJECTION and GL_TEXTURE stacks to be + * at least 2 but projectM already uses 2 stack-entries so overflows might be + * possible on older hardware. + * In contrast to this the GL_MODELVIEW stack-size is at least 32, but this + * function should be used for the modelview stack too. We cannot rely on a + * proper stack management of the underlying visualizer (projectM). + * For example in the projectM versions 1.0 - 1.01 the modelview- and + * projection-matrices were popped without being pushed first. + * + * By saving the whole stack we are on the safe side, so a nasty bug in the + * visualizer does not corrupt USDX. + *} +procedure TVideo_ProjectM.SaveMatrixStack(MatrixMode: GLenum; + var MatrixStack: TGLMatrixStack); +var + I: integer; + StackDepth: GLint; +begin + glMatrixMode(MatrixMode); + + StackDepth := GetMatrixStackDepth(MatrixMode); + SetLength(MatrixStack, StackDepth); + + // save current matrix stack + for I := StackDepth-1 downto 0 do + begin + // save current matrix + case (MatrixMode) of + GL_PROJECTION: + glGetDoublev(GL_PROJECTION_MATRIX, @MatrixStack[I]); + GL_MODELVIEW: + glGetDoublev(GL_MODELVIEW_MATRIX, @MatrixStack[I]); + GL_TEXTURE: + glGetDoublev(GL_TEXTURE_MATRIX, @MatrixStack[I]); + end; + + // remove matrix from stack + if (I > 0) then + glPopMatrix(); + end; + + // reset default (first) matrix + glLoadIdentity(); +end; + +{** + * Restores the OpenGL matrix stack stored with SaveMatrixStack. + *} +procedure TVideo_ProjectM.RestoreMatrixStack(MatrixMode: GLenum; + var MatrixStack: TGLMatrixStack); +var + I: integer; + StackDepth: GLint; +begin + glMatrixMode(MatrixMode); + + StackDepth := GetMatrixStackDepth(MatrixMode); + // remove all (except the first) matrices from current stack + for I := 1 to StackDepth-1 do + glPopMatrix(); + + // rebuild stack + for I := 0 to High(MatrixStack) do + begin + glLoadMatrixd(@MatrixStack[I]); + if (I < High(MatrixStack)) then + glPushMatrix(); + end; + + // clean stored stack + SetLength(MatrixStack, 0); +end; + +{** + * Saves the current OpenGL state. + * This is necessary to prevent projectM from corrupting USDX's current + * OpenGL state. + * + * The following steps are performed: + * - All attributes are pushed to the attribute-stack + * - Projection-/Texture-matrices are saved + * - Modelview-matrix is pushed to the Modelview-stack + * - the OpenGL error-state (glGetError) is cleared + *} +procedure TVideo_ProjectM.SaveOpenGLState(); +begin + // save all OpenGL state-machine attributes + glPushAttrib(GL_ALL_ATTRIB_BITS); + glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); + + SaveMatrixStack(GL_PROJECTION, fProjectionMatrixStack); + SaveMatrixStack(GL_MODELVIEW, fModelviewMatrixStack); + SaveMatrixStack(GL_TEXTURE, fTextureMatrixStack); + + glMatrixMode(GL_MODELVIEW); + + // reset OpenGL error-state + glGetError(); +end; + +{** + * Restores the OpenGL state saved by SaveOpenGLState() + * and resets the error-state. + *} +procedure TVideo_ProjectM.RestoreOpenGLState(); +begin + // reset OpenGL error-state + glGetError(); + + // restore matrix stacks + RestoreMatrixStack(GL_PROJECTION, fProjectionMatrixStack); + RestoreMatrixStack(GL_MODELVIEW, fModelviewMatrixStack); + RestoreMatrixStack(GL_TEXTURE, fTextureMatrixStack); + + // restore all OpenGL state-machine attributes + // (also restores the matrix mode) + glPopClientAttrib(); + glPopAttrib(); +end; + +procedure TVideo_ProjectM.InitProjectM; +begin + // the OpenGL state must be saved before TProjectM.Create is called + SaveOpenGLState(); + try + + try + {$IF PROJECTM_VERSION >= 1000000} // >= 1.0 + fPm := TProjectM.Create(fProjectMPath + 'config.inp'); + {$ELSE} + fPm := TProjectM.Create( + meshX, meshY, fps, textureSize, ScreenW, ScreenH, + fProjectMPath + 'presets', fProjectMPath + 'fonts'); + {$IFEND} + except on E: Exception do + begin + // Create() might fail if the config-file is not found + Log.LogError('TProjectM.Create: ' + E.Message, 'TVideoPlayback_ProjectM.VisualizerStart'); + Exit; + end; + end; + + // initialize OpenGL + fPm.ResetGL(ScreenW, ScreenH); + // skip projectM default-preset + fPm.RandomPreset(); + // projectM >= 1.0 uses the OpenGL FramebufferObject (FBO) extension. + // Unfortunately it does NOT reset the framebuffer-context after + // TProjectM.Create. Either glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) for + // a manual reset or TProjectM.RenderFrame() must be called. + // We use the latter so we do not need to load the FBO extension in USDX. + fPm.RenderFrame(); + finally + RestoreOpenGLState(); + end; +end; + +procedure TVideo_ProjectM.GetFrame(Time: Extended); +var + nSamples: cardinal; +begin + if (fState <> pmPlay) then + Exit; + + // get audio data + nSamples := AudioPlayback.GetPCMData(fPCMData); + + // generate some data if non is available + if (nSamples = 0) then + nSamples := GetRandomPCMData(fPCMData); + + // send audio-data to projectM + if (nSamples > 0) then + fPm.AddPCM16Data(PSmallInt(@fPCMData), nSamples); + + // store OpenGL state (might be messed up otherwise) + SaveOpenGLState(); + try + // setup projectM's OpenGL state + fPm.ResetGL(ScreenW, ScreenH); + + // let projectM render a frame + fPm.RenderFrame(); + + {$IFDEF UseTexture} + glBindTexture(GL_TEXTURE_2D, fVisualTex); + glFlush(); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, fVisualWidth, fVisualHeight, 0); + {$ENDIF} + finally + // restore USDX OpenGL state + RestoreOpenGLState(); + end; + + // discard projectM's depth buffer information (avoid overlay) + glClear(GL_DEPTH_BUFFER_BIT); +end; + +{** + * Draws the current frame to screen. + * TODO: this is not used yet. Data is directly drawn on GetFrame(). + *} +procedure TVideo_ProjectM.Draw(); +begin + {$IFDEF UseTexture} + // have a nice black background to draw on + if (fScreen = 1) then + begin + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + end; + + // exit if there's nothing to draw + if (fState <> pmPlay) then + Exit; + + // setup display + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + // Use count of screens instead of 1 for the right corner + // otherwise we would draw the visualization streched over both screens + // another point is that we draw over the at this time drawn first + // screen, if Screen = 2 + gluOrtho2D(0, Screens, 0, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBindTexture(GL_TEXTURE_2D, fVisualTex); + glColor4f(1, 1, 1, 1); + + // draw projectM frame + // Screen is 1 to 2. So current screen is from (Screen - 1) to (Screen) + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2f((fScreen - 1), 0); + glTexCoord2f(1, 0); glVertex2f(fScreen, 0); + glTexCoord2f(1, 1); glVertex2f(fScreen, 1); + glTexCoord2f(0, 1); glVertex2f((fScreen - 1), 1); + glEnd(); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + + // restore state + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + {$ENDIF} +end; + +procedure TVideo_ProjectM.DrawReflection(); +begin +end; + +{** + * Produces random "sound"-data in case no audio-data is available. + * Otherwise the visualization will look rather boring. + *} +function TVideo_ProjectM.GetRandomPCMData(var Data: TPCMData): Cardinal; +var + i: integer; +begin + // Produce some fake PCM data + if (fRndPCMcount mod 500 = 0) then + begin + FillChar(Data, SizeOf(TPCMData), 0); + end + else + begin + for i := 0 to 511 do + begin + Data[i][0] := Random(High(Word)+1); + Data[i][1] := Random(High(Word)+1); + end; + end; + Inc(fRndPCMcount); + Result := 512; +end; + + +initialization + MediaManager.Add(TVideoPlayback_ProjectM.Create); + +end. |