hedgewars/uRender.pas
changeset 10304 7e40820b7ed6
parent 10302 ea0b0e2efd95
child 10306 4fca8bcfaff0
--- a/hedgewars/uRender.pas	Sat Jun 14 07:12:22 2014 +0200
+++ b/hedgewars/uRender.pas	Sat Jun 14 15:49:44 2014 +0200
@@ -23,7 +23,10 @@
 
 interface
 
-uses SDLh, uTypes, GLunit, uConsts{$IFDEF GL2}, uMatrix{$ENDIF};
+uses SDLh, uTypes, GLunit;
+
+procedure initModule;
+procedure freeModule;
 
 procedure DrawSprite            (Sprite: TSprite; X, Y, Frame: LongInt);
 procedure DrawSprite            (Sprite: TSprite; X, Y, FrameX, FrameY: LongInt);
@@ -48,11 +51,13 @@
 
 procedure DrawLine              (X0, Y0, X1, Y1, Width: Single; color: LongWord); inline;
 procedure DrawLine              (X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
-procedure DrawFillRect          (r: TSDL_Rect);
+procedure DrawRect              (rect: TSDL_Rect; r, g, b, a: Byte; Fill: boolean);
 procedure DrawHedgehog          (X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
 procedure DrawScreenWidget      (widget: POnScreenWidget);
 procedure DrawWaterBody         (pVertexBuffer: Pointer);
 
+procedure RenderClear           ();
+procedure RenderSetClearColor      (r, g, b, a: real);
 procedure Tint                  (r, g, b, a: Byte); inline;
 procedure Tint                  (c: Longword); inline;
 procedure untint(); inline;
@@ -67,6 +72,10 @@
 procedure SetScale(f: GLfloat);
 procedure UpdateViewLimits();
 
+procedure RenderSetup();
+
+// TODO everything below this should not need a public interface
+
 procedure EnableTexture(enable:Boolean);
 
 procedure SetTexCoordPointer(p: Pointer;n: Integer);
@@ -86,7 +95,8 @@
 
 
 implementation
-uses uVariables;
+uses {$IFNDEF PAS2C} StrUtils, {$ENDIF}SysUtils, uVariables, uUtils, uConsts
+     {$IFDEF GL2}, uMatrix, uConsole{$ENDIF};
 
 {$IFDEF USE_TOUCH_INTERFACE}
 const
@@ -94,6 +104,12 @@
     MOVE_ANIM_TIME = 500;
 {$ENDIF}
 
+{$IFDEF GL2}
+var
+    shaderMain: GLuint;
+    shaderWater: GLuint;
+{$ENDIF}
+
 var LastTint: LongWord = 0;
 
 function isAreaOffscreen(X, Y, Width, Height: LongInt): boolean; inline;
@@ -115,6 +131,292 @@
     isDyAreaOffscreen:= 0;
 end;
 
+procedure RenderClear();
+begin
+    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+end;
+
+procedure RenderSetClearColor(r, g, b, a: real);
+begin
+    glClearColor(r, g, b, a);
+end;
+
+{$IFDEF GL2}
+function CompileShader(shaderFile: string; shaderType: GLenum): GLuint;
+var
+    shader: GLuint;
+    f: Textfile;
+    source, line: AnsiString;
+    sourceA: Pchar;
+    lengthA: GLint;
+    compileResult: GLint;
+    logLength: GLint;
+    log: PChar;
+begin
+    Assign(f, PathPrefix + cPathz[ptShaders] + '/' + shaderFile);
+    filemode:= 0; // readonly
+    Reset(f);
+    if IOResult <> 0 then
+    begin
+        AddFileLog('Unable to load ' + shaderFile);
+        halt(-1);
+    end;
+
+    source:='';
+    while not eof(f) do
+    begin
+        ReadLn(f, line);
+        source:= source + line + #10;
+    end;
+
+    Close(f);
+
+    WriteLnToConsole('Compiling shader: ' + PathPrefix + cPathz[ptShaders] + '/' + shaderFile);
+
+    sourceA:=PChar(source);
+    lengthA:=Length(source);
+
+    shader:=glCreateShader(shaderType);
+    glShaderSource(shader, 1, @sourceA, @lengthA);
+    glCompileShader(shader);
+    glGetShaderiv(shader, GL_COMPILE_STATUS, @compileResult);
+    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, @logLength);
+
+    if logLength > 1 then
+    begin
+        log := GetMem(logLength);
+        glGetShaderInfoLog(shader, logLength, nil, log);
+        WriteLnToConsole('========== Compiler log  ==========');
+        WriteLnToConsole(shortstring(log));
+        WriteLnToConsole('===================================');
+        FreeMem(log, logLength);
+    end;
+
+    if compileResult <> GL_TRUE then
+    begin
+        WriteLnToConsole('Shader compilation failed, halting');
+        halt(-1);
+    end;
+
+    CompileShader:= shader;
+end;
+
+function CompileProgram(shaderName: string): GLuint;
+var
+    program_: GLuint;
+    vs, fs: GLuint;
+    linkResult: GLint;
+    logLength: GLint;
+    log: PChar;
+begin
+    program_:= glCreateProgram();
+    vs:= CompileShader(shaderName + '.vs', GL_VERTEX_SHADER);
+    fs:= CompileShader(shaderName + '.fs', GL_FRAGMENT_SHADER);
+    glAttachShader(program_, vs);
+    glAttachShader(program_, fs);
+
+    glBindAttribLocation(program_, aVertex, PChar('vertex'));
+    glBindAttribLocation(program_, aTexCoord, PChar('texcoord'));
+    glBindAttribLocation(program_, aColor, PChar('color'));
+
+    glLinkProgram(program_);
+    glDeleteShader(vs);
+    glDeleteShader(fs);
+
+    glGetProgramiv(program_, GL_LINK_STATUS, @linkResult);
+    glGetProgramiv(program_, GL_INFO_LOG_LENGTH, @logLength);
+
+    if logLength > 1 then
+    begin
+        log := GetMem(logLength);
+        glGetProgramInfoLog(program_, logLength, nil, log);
+        WriteLnToConsole('========== Compiler log  ==========');
+        WriteLnToConsole(shortstring(log));
+        WriteLnToConsole('===================================');
+        FreeMem(log, logLength);
+    end;
+
+    if linkResult <> GL_TRUE then
+    begin
+        WriteLnToConsole('Linking program failed, halting');
+        halt(-1);
+    end;
+
+    CompileProgram:= program_;
+end;
+{$ENDIF}
+
+procedure RenderSetup();
+var AuxBufNum: LongInt = 0;
+    tmpstr: ansistring;
+    tmpint: LongInt;
+    tmpn: LongInt;
+begin
+    // suppress hint/warning
+    AuxBufNum:= AuxBufNum;
+
+    // get the max (h and v) size for textures that the gpu can support
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, @MaxTextureSize);
+    if MaxTextureSize <= 0 then
+        begin
+        MaxTextureSize:= 1024;
+        AddFileLog('OpenGL Warning - driver didn''t provide any valid max texture size; assuming 1024');
+        end
+    else if (MaxTextureSize < 1024) and (MaxTextureSize >= 512) then
+        begin
+        cReducedQuality := cReducedQuality or rqNoBackground;
+        AddFileLog('Texture size too small for backgrounds, disabling.');
+        end;
+    // everyone loves debugging
+    // find out which gpu we are using (for extension compatibility maybe?)
+    AddFileLog('OpenGL-- Renderer: ' + shortstring(pchar(glGetString(GL_RENDERER))));
+    AddFileLog('  |----- Vendor: ' + shortstring(pchar(glGetString(GL_VENDOR))));
+    AddFileLog('  |----- Version: ' + shortstring(pchar(glGetString(GL_VERSION))));
+    AddFileLog('  |----- Texture Size: ' + inttostr(MaxTextureSize));
+{$IFDEF USE_VIDEO_RECORDING}
+    glGetIntegerv(GL_AUX_BUFFERS, @AuxBufNum);
+    AddFileLog('  |----- Number of auxiliary buffers: ' + inttostr(AuxBufNum));
+{$ENDIF}
+{$IFNDEF PAS2C}
+    AddFileLog('  \----- Extensions: ');
+
+    // fetch extentions and store them in string
+    tmpstr := StrPas(PChar(glGetString(GL_EXTENSIONS)));
+    tmpn := WordCount(tmpstr, [' ']);
+    tmpint := 1;
+
+    repeat
+    begin
+        // print up to 3 extentions per row
+        // ExtractWord will return empty string if index out of range
+        AddFileLog(TrimRight(
+            ExtractWord(tmpint, tmpstr, [' ']) + ' ' +
+            ExtractWord(tmpint+1, tmpstr, [' ']) + ' ' +
+            ExtractWord(tmpint+2, tmpstr, [' '])
+        ));
+        tmpint := tmpint + 3;
+    end;
+    until (tmpint > tmpn);
+{$ENDIF}
+    AddFileLog('');
+
+    defaultFrame:= 0;
+
+{$IFDEF USE_VIDEO_RECORDING}
+    if GameType = gmtRecord then
+    begin
+        if glLoadExtension('GL_EXT_framebuffer_object') then
+        begin
+            CreateFramebuffer(defaultFrame, depthv, texv);
+            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame);
+            AddFileLog('Using framebuffer for video recording.');
+        end
+        else if AuxBufNum > 0 then
+        begin
+            glDrawBuffer(GL_AUX0);
+            glReadBuffer(GL_AUX0);
+            AddFileLog('Using auxiliary buffer for video recording.');
+        end
+        else
+        begin
+            glDrawBuffer(GL_BACK);
+            glReadBuffer(GL_BACK);
+            AddFileLog('Warning: off-screen rendering is not supported; using back buffer but it may not work.');
+        end;
+    end;
+{$ENDIF}
+
+{$IFDEF GL2}
+
+{$IFDEF PAS2C}
+    err := glewInit();
+    if err <> GLEW_OK then
+    begin
+        WriteLnToConsole('Failed to initialize GLEW.');
+        halt;
+    end;
+{$ENDIF}
+
+{$IFNDEF PAS2C}
+    if not Load_GL_VERSION_2_0 then
+        halt;
+{$ENDIF}
+
+    shaderWater:= CompileProgram('water');
+    glUseProgram(shaderWater);
+    glUniform1i(glGetUniformLocation(shaderWater, pchar('tex0')), 0);
+    uWaterMVPLocation:= glGetUniformLocation(shaderWater, pchar('mvp'));
+
+    shaderMain:= CompileProgram('default');
+    glUseProgram(shaderMain);
+    glUniform1i(glGetUniformLocation(shaderMain, pchar('tex0')), 0);
+    uMainMVPLocation:= glGetUniformLocation(shaderMain, pchar('mvp'));
+    uMainTintLocation:= glGetUniformLocation(shaderMain, pchar('tint'));
+
+    uCurrentMVPLocation:= uMainMVPLocation;
+
+    Tint(255, 255, 255, 255);
+    UpdateModelviewProjection;
+{$ENDIF}
+
+{$IFNDEF USE_S3D_RENDERING}
+    if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then
+    begin
+        // prepare left and right frame buffers and associated textures
+        if glLoadExtension('GL_EXT_framebuffer_object') then
+            begin
+            CreateFramebuffer(framel, depthl, texl);
+            CreateFramebuffer(framer, depthr, texr);
+
+            // reset
+            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame)
+            end
+        else
+            cStereoMode:= smNone;
+    end;
+{$ENDIF}
+
+// set view port to whole window
+glViewport(0, 0, cScreenWidth, cScreenHeight);
+
+{$IFDEF GL2}
+    uMatrix.initModule;
+    hglMatrixMode(MATRIX_MODELVIEW);
+    // prepare default translation/scaling
+    hglLoadIdentity();
+    hglScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
+    hglTranslatef(0, -cScreenHeight / 2, 0);
+
+    EnableTexture(True);
+
+    glEnableVertexAttribArray(aVertex);
+    glEnableVertexAttribArray(aTexCoord);
+    glGenBuffers(1, @vBuffer);
+    glGenBuffers(1, @tBuffer);
+    glGenBuffers(1, @cBuffer);
+{$ELSE}
+    glMatrixMode(GL_MODELVIEW);
+    // prepare default translation/scaling
+    glLoadIdentity();
+    glScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
+    glTranslatef(0, -cScreenHeight / 2, 0);
+
+    // disable/lower perspective correction (will not need it anyway)
+    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
+    // disable dithering
+    glDisable(GL_DITHER);
+    // enable common states by default as they save a lot
+    glEnable(GL_TEXTURE_2D);
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+{$ENDIF}
+
+    // enable alpha blending
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    // disable/lower perspective correction (will not need it anyway)
+end;
+
 procedure openglLoadIdentity(); inline;
 begin
 {$IFDEF GL2}
@@ -285,11 +587,25 @@
     tmp:= cScreenHeight / cScaleFactor;
     ViewBottomY:= round(tmp) + cScreenHeight div 2; // ceil could make more sense
     ViewTopY:= round(-tmp) + cScreenHeight div 2; // floor could make more sense
+
+    // visual debugging fun :D
+    if cViewLimitsDebug then
+        begin
+        // some margin on each side
+        tmp:= min(cScreenWidth, cScreenHeight) div 2 / cScaleFactor;
+        ViewLeftX  := ViewLeftX   + trunc(tmp);
+        ViewRightX := ViewRightX  - trunc(tmp);
+        ViewBottomY:= ViewBottomY - trunc(tmp);
+        ViewTopY   := ViewTopY    + trunc(tmp);
+        end;
+
+    ViewWidth := ViewRightX  - ViewLeftX + 1;
+    ViewHeight:= ViewBottomY - ViewTopY  + 1;
 end;
 
 procedure SetScale(f: GLfloat);
 begin
-// leave immediately if scale factor did not change
+    // leave immediately if scale factor did not change
     if f = cScaleFactor then
         exit;
 
@@ -303,7 +619,7 @@
         openglPushMatrix; // save default scaling in matrix
         openglLoadIdentity();
         openglScalef(f / cScreenWidth, -f / cScreenHeight, 1.0);
-        openglTranslatef(0, -cScreenHeight / 2, 0);
+        openglTranslatef(0, -cScreenHeight div 2, 0);
         end;
 
     cScaleFactor:= f;
@@ -337,11 +653,16 @@
 if (SourceTexture^.h = 0) or (SourceTexture^.w = 0) then
     exit;
 
+{if isDxAreaOffscreen(X, W) <> 0 then
+    exit;
+if isDyAreaOffscreen(Y, H) <> 0 then
+    exit;}
+
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
-if (abs(X) > W) and ((abs(X + W / 2) - W / 2) > cScreenWidth / cScaleFactor) then
+{if (abs(X) > W) and ((abs(X + W / 2) - W / 2) > cScreenWidth / cScaleFactor) then
     exit;
 if (abs(Y) > H) and ((abs(Y + H / 2 - (0.5 * cScreenHeight)) - H / 2) > cScreenHeight / cScaleFactor) then
-    exit;
+    exit;}
 
 rr.x:= X;
 rr.y:= Y;
@@ -357,13 +678,13 @@
 
 if Dir < 0 then
     begin
-    VertexBuffer[0].X:= X + rr.w/2;
+    VertexBuffer[0].X:= X + rr.w div 2;
     VertexBuffer[0].Y:= Y;
-    VertexBuffer[1].X:= X - rr.w/2;
+    VertexBuffer[1].X:= X - rr.w div 2;
     VertexBuffer[1].Y:= Y;
-    VertexBuffer[2].X:= X - rr.w/2;
+    VertexBuffer[2].X:= X - rr.w div 2;
     VertexBuffer[2].Y:= rr.h + Y;
-    VertexBuffer[3].X:= X + rr.w/2;
+    VertexBuffer[3].X:= X + rr.w div 2;
     VertexBuffer[3].Y:= rr.h + Y;
     end
 else
@@ -402,16 +723,16 @@
 
 openglPushMatrix;
 openglTranslatef(X, Y, 0);
-openglScalef(Scale, Scale, 1);
+
+if Scale <> 1.0 then
+    openglScalef(Scale, Scale, 1);
 
 glBindTexture(GL_TEXTURE_2D, Texture^.id);
 
 SetVertexPointer(@Texture^.vb, Length(Texture^.vb));
 SetTexCoordPointer(@Texture^.tb, Length(Texture^.vb));
 
-{$IFDEF GL2}
 UpdateModelviewProjection;
-{$ENDIF}
 
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(Texture^.vb));
 openglPopMatrix;
@@ -457,11 +778,17 @@
     hw, hh, nx, ny: LongInt;
     VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
 begin
+
+if isDxAreaOffscreen(X, w) <> 0 then
+    exit;
+if isDyAreaOffscreen(Y, h) <> 0 then
+    exit;
+
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
-if (abs(X) > W) and ((abs(X + dir * OffsetX) - W / 2) * cScaleFactor > cScreenWidth) then
+{if (abs(X) > W) and ((abs(X + dir * OffsetX) - W / 2) * cScaleFactor > cScreenWidth) then
     exit;
 if (abs(Y) > H) and ((abs(Y + OffsetY - (0.5 * cScreenHeight)) - W / 2) * cScaleFactor > cScreenHeight) then
-    exit;
+    exit;}
 
 openglPushMatrix;
 
@@ -558,11 +885,17 @@
 procedure DrawTextureRotated(Texture: PTexture; hw, hh, X, Y, Dir: LongInt; Angle: real);
 var VertexBuffer: array [0..3] of TVertex2f;
 begin
+
+if isDxAreaOffscreen(X, 2 * hw) <> 0 then
+    exit;
+if isDyAreaOffscreen(Y, 2 * hh) <> 0 then
+    exit;
+
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
-if (abs(X) > 2 * hw) and ((abs(X) - hw) > cScreenWidth / cScaleFactor) then
+{if (abs(X) > 2 * hw) and ((abs(X) - hw) > cScreenWidth / cScaleFactor) then
     exit;
 if (abs(Y) > 2 * hh) and ((abs(Y - 0.5 * cScreenHeight) - hh) > cScreenHeight / cScaleFactor) then
-    exit;
+    exit;}
 
 openglPushMatrix;
 openglTranslatef(X, Y, 0);
@@ -647,12 +980,20 @@
 
 procedure DrawTextureCentered(X, Top: LongInt; Source: PTexture);
 var scale: GLfloat;
+    left : LongInt;
 begin
+    // scale down if larger than screen
     if (Source^.w + 20) > cScreenWidth then
-        scale:= cScreenWidth / (Source^.w + 20)
+        begin
+        scale:= cScreenWidth / (Source^.w + 20);
+        DrawTexture(X - round(Source^.w * scale) div 2, Top, Source, scale);
+        end
     else
-        scale:= 1.0;
-    DrawTexture(X - round(Source^.w * scale) div 2, Top, Source, scale)
+        begin
+        left:= X - Source^.w div 2;
+        if (not isAreaOffscreen(left, Top, Source^.w, Source^.h)) then
+            DrawTexture(left, Top, Source);
+        end;
 end;
 
 procedure DrawLine(X0, Y0, X1, Y1, Width: Single; color: LongWord); inline;
@@ -690,31 +1031,36 @@
     glDisable(GL_LINE_SMOOTH);
 end;
 
-procedure DrawFillRect(r: TSDL_Rect);
+procedure DrawRect(rect: TSDL_Rect; r, g, b, a: Byte; Fill: boolean);
 var VertexBuffer: array [0..3] of TVertex2f;
 begin
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
-
-if (abs(r.x) > r.w) and ((abs(r.x + r.w / 2) - r.w / 2) * cScaleFactor > cScreenWidth) then
+{if (abs(r.x) > r.w) and ((abs(r.x + r.w / 2) - r.w / 2) * cScaleFactor > cScreenWidth) then
     exit;
 if (abs(r.y) > r.h) and ((abs(r.y + r.h / 2 - (0.5 * cScreenHeight)) - r.h / 2) * cScaleFactor > cScreenHeight) then
-    exit;
+    exit;}
 
 EnableTexture(False);
 
-Tint($00, $00, $00, $80);
+Tint(r, g, b, a);
 
-VertexBuffer[0].X:= r.x;
-VertexBuffer[0].Y:= r.y;
-VertexBuffer[1].X:= r.x + r.w;
-VertexBuffer[1].Y:= r.y;
-VertexBuffer[2].X:= r.x + r.w;
-VertexBuffer[2].Y:= r.y + r.h;
-VertexBuffer[3].X:= r.x;
-VertexBuffer[3].Y:= r.y + r.h;
+with rect do
+begin
+    VertexBuffer[0].X:= x;
+    VertexBuffer[0].Y:= y;
+    VertexBuffer[1].X:= x + w;
+    VertexBuffer[1].Y:= y;
+    VertexBuffer[2].X:= x + w;
+    VertexBuffer[2].Y:= y + h;
+    VertexBuffer[3].X:= x;
+    VertexBuffer[3].Y:= y + h;
+end;
 
 SetVertexPointer(@VertexBuffer[0], Length(VertexBuffer));
-glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
+if Fill then
+    glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer))
+else
+    glDrawArrays(GL_LINE_LOOP, 0, Length(VertexBuffer));
 
 untint;
 
@@ -953,4 +1299,18 @@
         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 end;
 
+procedure initModule;
+begin
+end;
+
+procedure freeModule;
+begin
+{$IFDEF GL2}
+    glDeleteProgram(shaderMain);
+    glDeleteProgram(shaderWater);
+    glDeleteBuffers(1, @vBuffer);
+    glDeleteBuffers(1, @tBuffer);
+    glDeleteBuffers(1, @cBuffer);
+{$ENDIF}
+end;
 end.