GLip - OpenGL Image Render

Share your advanced PureBasic knowledge/code with the community.
pjay
Enthusiast
Enthusiast
Posts: 252
Joined: Thu Mar 30, 2006 11:14 am

GLip - OpenGL Image Render

Post by pjay »

GLip is a small module that can assist with displaying PB images (much like the ImageGadget) but is faster in situations where you need to scale the image. i.e. to compensate for DPI awareness or show fullscreen.

Code: Select all

; Purebasic module: GLip v1.01 (Simplification extract of GLipX module) - Phil James 2023.  Tested on Windows 11 x64 only.
; Filename: GLip module.pbi

; Purpose: Enhance performance of displaying a PB image when needing to scale for Gadget size, DPI awareness or full-screen rendering.
;        - Fast alternative to Imagegadget.

; Basic usage:
;    GLIP::Set(#GL_GadgetID, #PB_Image [, Optional: FillMode, Scalemode, BackgroundColour])
;      RGBA value of background - Default: RGBA(0,0,0,255)
;      ScaleMode: #PB_Image_Raw or #PB_Image_Smooth (default)
;      FillMode: see #Glip_Fillmode enumerates - defaults to #Glip_FillMode_Stretch.

; Supplemental:
;   GLIP::Set_Array(#GL_GadgetID, *Array_Ptr, w, h, bpp [, Optional: Scalemode, BackgroundColour, FillMode])
;     draws directly from an array

; Update v1.01 - reduced arguments & brought forward FillMode argument

DeclareModule GLIP
  Enumeration Glip_Fillmode
    #Glip_FillMode_Centre ; centres the image, will crop or create borders if image dimensions != gadget dimensions.
    #Glip_FillMode_Fill   ; Shrinks or enlarges image to fit on shortest edge, crops if image dimensions != gadget dimensions.
    #Glip_FillMode_Fit    ; Shrinks or enlarges image to fit on longest edge, creates border if image dimensions != gadget dimensions.
    #Glip_FillMode_Stretch; Shrinks or enlarges image to fit gadget dimensions, loses aspect ratio if image dimensions != gadget dimensions.
  EndEnumeration
  
  Declare Set(Gadget.i, Image.i, FillMode = #Glip_FillMode_Stretch, ScaleMode = #PB_Image_Smooth, BGCol = -16777216) ;/ set a PB image to a gl gadget
  Declare Set_Array(Gadget.i, *Im, w, h, bpp, FillMode = #Glip_FillMode_Stretch, ScaleMode = #PB_Image_Smooth, BGCol = -16777216) ;/ sets an image array to a gl Gadget
  Macro gImageGadget(Gad,x,y,w,h,img)
    OpenGLGadget(Gad,x,y,w,h,#PB_OpenGL_Keyboard|#PB_OpenGL_NoAccumulationBuffer|#PB_OpenGL_NoDepthBuffer|#PB_OpenGL_NoStencilBuffer|#PB_OpenGL_NoFlipSynchronization)
    GLIP::Set(Gad,Img)
  EndMacro
EndDeclareModule
Module GLIP
  Procedure _Common_Init(ScaleMode.i)
    Protected TextureID0.i
    glEnable_(#GL_TEXTURE_2D)
    glGenTextures_(1, @TextureID0);
    glBindTexture_(#GL_TEXTURE_2D, TextureID0);
    If ScaleMode = #PB_Image_Raw
      glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MAG_FILTER, #GL_NEAREST) : glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MIN_FILTER, #GL_NEAREST); 
    Else
      glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MAG_FILTER, #GL_LINEAR) : glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MIN_FILTER, #GL_LINEAR); 
    EndIf
    ProcedureReturn TextureID0
  EndProcedure
  Procedure _Common_Render(Gadget, TextureID0, W, H, FillMode, BGCol, FlipY = #True)
    Structure Pointf : x.f : y.f : EndStructure
    Static Dim Vc.Pointf(3) : Static Dim Tc.Pointf(3) : Protected AdjX.f, AdjY.f, MyLoop
    
    VC(0)\x = -W/2 : VC(0)\y = -H/2 : VC(1)\x = W/2 : VC(1)\y = -H/2 : VC(2)\x = W/2 : VC(2)\y = H/2 : VC(3)\x = -W/2 : VC(3)\y = H/2
    If FlipY = #True
      TC(0)\x = 0 : TC(0)\y = 1 : TC(1)\x = 1 : TC(1)\y = 1 : TC(2)\x = 1 : TC(2)\y = 0 : TC(3)\x = 0 : TC(3)\y = 0      
    Else
      TC(0)\x = 0 : TC(0)\y = 0 : TC(1)\x = 1 : TC(1)\y = 0 : TC(2)\x = 1 : TC(2)\y = 1 : TC(3)\x = 0 : TC(3)\y = 1      
    EndIf
    
    glClearColor_(Red(BGCol)/255.0,Green(BGCol)/255.0,Blue(BGCol)/255.0,Alpha(BGCol)/255.0)
    glClear_(#GL_COLOR_BUFFER_BIT)
    glDisable_(#GL_DEPTH_TEST) : glEnable_(#GL_CULL_FACE) : glCullFace_(#GL_FRONT)    
    
    AdjX = W / GadgetWidth(Gadget) : AdjY = H / GadgetHeight(Gadget) ; centered
    Select FillMode
      Case #Glip_FillMode_Centre ; centres image to gadget, value set as default
      Case #Glip_FillMode_Fill   ; 1 axis cropped if AR different than gadgets
        If GadgetWidth(Gadget) / W > GadgetHeight(Gadget) / H
          AdjX * (GadgetWidth(Gadget) / W) : AdjY * (GadgetWidth(Gadget) / W)
        Else
          AdjX * (GadgetHeight(Gadget) / H) : AdjY * (GadgetHeight(Gadget) / H)
        EndIf
      Case #Glip_FillMode_Fit ; maintains AR, borders present if AR different than gadgets
        If AdjX < AdjY
          AdjX * (GadgetHeight(Gadget) / H) : AdjY * (GadgetHeight(Gadget) / H)          
        Else
          AdjX * (GadgetWidth(Gadget) / W) : AdjY * (GadgetWidth(Gadget) / W)      
        EndIf
      Default ; Case #Glip_FillMode_Stretch
        AdjX = 1.0 : AdJy = 1.0
    EndSelect
    glMatrixMode_(#GL_PROJECTION) : glLoadIdentity_() : glOrtho_(-W/2,W/2,H/2,-H/2,-1,1)
    
    glBegin_(#GL_QUADS) 
    For MyLoop = 0 To 3 : glTexCoord2f_(TC(Myloop)\x,TC(Myloop)\y) : glVertex2f_(VC(MyLoop)\x * AdjX, VC(MyLoop)\y * AdjY) : Next
    glEnd_()
    
    glDeleteTextures_(1,@TextureID0)
    SetGadgetAttribute(Gadget,#PB_OpenGL_FlipBuffers,#True)
  EndProcedure
  Procedure Set(Gadget.i, Image.i, FillMode = #Glip_FillMode_Stretch, ScaleMode = #PB_Image_Smooth, BGCol = -16777216)
    Protected TextureID0.i, W, H
    If IsGadget(Gadget) And IsImage(Image)
      If GadgetType(Gadget) = #PB_GadgetType_OpenGL
        SetGadgetAttribute(Gadget,#PB_OpenGL_SetContext,#True)
        TextureID0 = _Common_Init(ScaleMode)
        StartDrawing(ImageOutput(Image))
        W = OutputWidth() : H = OutputHeight()
        glPixelStorei_(#GL_PACK_ROW_LENGTH,DrawingBufferPitch())
        If OutputDepth() = 24
          glTexImage2D_(#GL_TEXTURE_2D, 0,  #GL_RGB, W, H, 0, #GL_BGR_EXT, #GL_UNSIGNED_BYTE, DrawingBuffer());  
        Else
          glTexImage2D_(#GL_TEXTURE_2D, 0,  #GL_RGBA, W, H, 0, #GL_BGRA_EXT, #GL_UNSIGNED_BYTE, DrawingBuffer());  
        EndIf
        _Common_Render(Gadget, TextureID0, W, H, FillMode, BGCol)
        StopDrawing()
      EndIf
    EndIf
  EndProcedure
  Procedure Set_Array(Gadget.i, *Array_Ptr, w, h, bpp, FillMode = #Glip_FillMode_Stretch, ScaleMode = #PB_Image_Smooth, BGCol = -16777216) ;/ for 8 bit or 32 bit array data.
    Protected Format = #GL_BGR_EXT, Type = #GL_RGB, TextureID0.i
    If bpp = 8 : Format = #GL_RED : Type = #GL_LUMINANCE : ElseIf bpp = 32 : Format = #GL_BGRA_EXT : Type = #GL_RGBA : EndIf
    If IsGadget(Gadget)
      If GadgetType(Gadget) = #PB_GadgetType_OpenGL
        SetGadgetAttribute(Gadget,#PB_OpenGL_SetContext,#True)
        TextureID0 = _Common_Init(ScaleMode)
        glTexImage2D_(#GL_TEXTURE_2D, 0, Type, W, H, 0, Format, #GL_UNSIGNED_BYTE, *Array_Ptr); 
        _Common_Render(Gadget, TextureID0, W, H, FillMode, BGCol, #False)
      EndIf
    EndIf
  EndProcedure
EndModule

CompilerIf #PB_Compiler_IsMainFile ; small demo app to demonstrate timing differences between PB image scaling performance and GLIP
  
  Enumeration ;/ windows / image / gadget
    #MyWindow_Main
    
    #MyImage_Main = 0
    #MyImage_Resized
    
    #MyGad_Method_Combobox = 0
    #MyGad_Method_Combobox_text
    
    #MyGad_Scale_Combobox
    #MyGad_Bit_Combobox
    
    #MyGad_Canvas
    #MyGad_GLIP
    #MyGad_Text
  EndEnumeration
  
  #Mode_Fast = #PB_OpenGL_NoAccumulationBuffer|#PB_OpenGL_NoStencilBuffer|#PB_OpenGL_NoDepthBuffer|#PB_OpenGL_NoFlipSynchronization
  #Mode_Smooth = #PB_OpenGL_NoAccumulationBuffer|#PB_OpenGL_NoStencilBuffer|#PB_OpenGL_NoDepthBuffer|#PB_OpenGL_FlipSynchronization
  
  #UseMode = #Mode_Fast
  ;#UseMode = #Mode_Smooth ; #Mode_Smooth may add vsync delay to the rendering time (depending on system configuration) 
  
  Global Event.i, Frame, Time, AvgTime.f, Width = 640, Height = 180, Col, txt.s
  ;Global Event.i, Frame, Time, AvgTime.f, Width = 1920, Height = 1080, Col, txt.s
  OpenWindow(#MyWindow_Main,0,0,width,height,"glIp Demo: Phil James 2023",#PB_Window_SystemMenu|#PB_Window_BorderLess|#PB_Window_Maximize|#PB_Window_MaximizeGadget)
  
  CreateImage(#MyImage_Main,Width,Height,32)
  AddWindowTimer(#MyWindow_Main,1,1000)
  LoadFont(0,"Impact",16) : SetGadgetFont(#PB_Default,FontID(0))
  
  TextGadget(#MyGad_Method_Combobox_Text,2,4,210,30,"Presentation Method:")
  ComboBoxGadget(#MyGad_Method_Combobox,212,0,100,32)
  AddGadgetItem(#MyGad_Method_Combobox,-1,"Canvas")
  AddGadgetItem(#MyGad_Method_Combobox,-1,"GLIp")
  SetGadgetState(#MyGad_Method_Combobox,0)
  
  ComboBoxGadget(#MyGad_Scale_Combobox,WindowWidth(#MyWindow_Main)-160,0,154,32)
  AddGadgetItem(#MyGad_Scale_Combobox,-1,"Scale: Centre")
  AddGadgetItem(#MyGad_Scale_Combobox,-1,"Scale: Fill")
  AddGadgetItem(#MyGad_Scale_Combobox,-1,"Scale: Fit")
  AddGadgetItem(#MyGad_Scale_Combobox,-1,"Scale: Stretch")
  SetGadgetState(#MyGad_Scale_Combobox,3) : HideGadget(#MyGad_Scale_Combobox,1)
  
  ComboBoxGadget(#MyGad_Bit_Combobox,320,0,54,32)
  AddGadgetItem(#MyGad_Bit_Combobox,-1,"24")
  AddGadgetItem(#MyGad_Bit_Combobox,-1,"32")
  SetGadgetState(#MyGad_Bit_Combobox,1)
  
  TextGadget(#MyGad_Text,380,4,800,26,"")
  CanvasGadget(#MyGad_Canvas,0,32,WindowWidth(#MyWindow_Main),WindowHeight(#MyWindow_Main)-32)
  
  OpenGLGadget(#MyGad_GLIP,0,32,WindowWidth(#MyWindow_Main),WindowHeight(#MyWindow_Main)-32,#UseMode)
  txt = "bit - Time To display image (" + Str(Width) + "x" + Str(Height) + " @ "+Str(GadgetWidth(#MyGad_GLIP) * DesktopResolutionX())+"x"+Str(GadgetHeight(#MyGad_GLIP) * DesktopResolutionY())+"): " 
  
  HideGadget(#MyGad_GLIP,1)
  
  Procedure Render()
    Static Rotation.f
    Frame + 1 ;/ increment frame number for timing average
    Rotation + 0.025
    StartVectorDrawing(ImageVectorOutput(#MyImage_Main))
    VectorSourceColor(RGBA(150,140,130,255)) : FillVectorOutput()
    Col = 127.0 + Sin(ElapsedMilliseconds() / 1000.0) * 127
    VectorSourceColor(RGBA(Col,190,160,255))
    AddPathCircle((Width * 0.5) + (Width * 0.3) * Cos(Rotation),(height * 0.5) + (height * 0.3) * Sin(Rotation),height * 0.1) : FillPath()
    
    VectorSourceColor(RGBA(250,50,50,255)) ;/ outer red box to check that scaling is working correctly
    AddPathBox(0,0,VectorOutputWidth(),VectorOutputHeight()) : StrokePath(5)
    
    StopVectorDrawing()
    time = ElapsedMilliseconds()
    Select GetGadgetState(#MyGad_Method_Combobox)
      Case 0 ;/ canvas render
        StartDrawing(CanvasOutput(#MyGad_Canvas))
        DrawImage(ImageID(#MyImage_Main),0,0,OutputWidth(), OutputHeight())
        StopDrawing()
      Case 1 ;/ GLIP
        GLIP::Set(#MyGad_GLIP,#MyImage_Main,GetGadgetState(#MyGad_Scale_Combobox),#PB_Image_Smooth, RGBA(0,30,40,255))
    EndSelect
    AvgTime + (ElapsedMilliseconds() - Time)
  EndProcedure
  
  Repeat
    Repeat
      Event = WindowEvent()
      Select Event
        Case #PB_Event_CloseWindow : End
        Case #PB_Event_Timer
          If Frame > 0
            SetGadgetText(#MyGad_Text,Txt + StrF(Avgtime / Frame,2) + "ms - (Theoretical FPS: "+StrF(1000/(Avgtime / Frame),1)+")")
          EndIf
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #MyGad_Bit_Combobox
              Frame = 0 : Avgtime = 0 ;/ reset timing information
              If GetGadgetState(#MyGad_Bit_Combobox) = 0
                CreateImage(#MyImage_Main,Width,Height,24)
              Else
                CreateImage(#MyImage_Main,Width,Height,32)
              EndIf
            Case #MyGad_Method_Combobox 
              If EventType() = #PB_EventType_Change
                Frame = 0 : Avgtime = 0 ; reset timing information
                Select GetGadgetState(#MyGad_Method_Combobox)
                  Case 0 : HideGadget(#MyGad_GLIP,1)   : HideGadget(#MyGad_Canvas,0) : HideGadget(#MyGad_Scale_Combobox,1)
                  Case 1 : HideGadget(#MyGad_Canvas,1) : HideGadget(#MyGad_GLIP,0)   : HideGadget(#MyGad_Scale_Combobox,0)
                EndSelect
              EndIf
          EndSelect
      EndSelect
    Until Event = 0
    Render()
  ForEver
CompilerEndIf

A small demonstration of using the Set_Array() function:

Code: Select all

; CPU Shader - Blurred Lines.pb -  Converted from: https://www.shadertoy.com/view/DtXfDr (Supah) - Phil James 10/2023
; Uses threads so recommend enabling threadsafe compiler option.
EnableExplicit

XIncludeFile "..\Includes\GLip module.pbi"

Enumeration ;/ window
  #Window_Main
EndEnumeration
Enumeration ;/ gadgets
  #Gad_GL
EndEnumeration

Structure RGBf : R.f : G.f : B.f : EndStructure
Structure Pointf : X.f : Y.f : EndStructure
Structure Threads_Structure : ThreadID.i : Y_Start.l : Y_End.l : Timef.f : EndStructure

ExamineDesktops()
Global Downscale.f = 16, MinScale.f = 1, MaxScale.f = 8, DynamicScale = #True
Global OnFrame, Exit, Time.f, TimeF.f = 3, SizeX = DesktopWidth(0) / Downscale, SizeY = SizeX / 2

Global Dim ImageArray_Main.l(SizeY-1,SizeX-1), Event, ThreadCount = CountCPUs(#PB_System_ProcessCPUs)
Global Dim Render_ThreadControl.Threads_Structure(ThreadCount)

Macro SmoothstepM(t) : t * t * (3.0 - 2.0 * t) : EndMacro
Macro ClampM(value, min, max) : If value < min : value = min : ElseIf value > max : value = max : EndIf : EndMacro

Procedure Render_Thread(*Thread.Threads_Structure)
  Protected X, Y, high.f, low.f, Time.f = *Thread\Timef, i.f, u.pointf, a.pointf, t.f, ss1.f, ss2.f, ss3.f, ss4.f, o.RGBf, w.f, v.f
  For Y = *Thread\Y_Start To *Thread\y_end
    U\y = (y - 0.5 * SizeY) / SizeY : A\y = Abs(U\y);
    For X = 0 To SizeX-1
      U\x = (x - 0.5 * SizeX) / SizeY : A\x = Abs(U\x) : ClampM(a\x,0.0,1.0)
      O\r = 0 : O\g = 0 : o\b = 0 : t = 0
      ss2 = 1.0 - SmoothstepM(a\x)
      If a\x < 0.3 : ss3 = 1.0 - SmoothstepM(0.3) : Else : ss3 = 1.0 - SmoothstepM(a\x) : EndIf
      ClampM(a\x,0.2,0.9) : ss1 = SmoothstepM(a\x)
      Low = 0.06 * ss1    
      While t <= 1 : t + 0.2
        w.f = Abs(U\y + 0.2 * ss2 * Sin(Time * (1.0 + t) + U\x * (8.0+t))) - 0.003
        v = (w - low) / -Low : ClampM(v,0.0,1.0) : ss4 = ss3 * SmoothStepM(v)
        O\r + ss4 * ( 0.2 + (t*0.8)) : O\g + ss4 * ( 0.2 + (t*0.5)) : O\b + ss4 * ( 0.2 + (t*0.2))
      Wend
      ClampM(o\r,0,1) : ClampM(o\g,0,1) : ClampM(o\b,0,1)
      ImageArray_Main(Y,x) = RGB(o\b * 255.0,o\g * 255.0, o\r * 255.0)
    Next
  Next
EndProcedure
Procedure Render_Main()
  Protected MyLoop, StrideY.i = Round(SizeY / ThreadCount,#PB_Round_Up), Y = 0 : Static BadFrame
  TimeF  + 0.01
  For MyLoop = 1 To ThreadCount
    Render_ThreadControl(Myloop)\Y_Start = Y : Y + StrideY
    Render_ThreadControl(Myloop)\Y_End = Y - 1
    If Render_ThreadControl(Myloop)\Y_End > SizeY-1 : Render_ThreadControl(Myloop)\Y_End = SizeY-1 : EndIf    
    Render_ThreadControl(MyLoop)\Timef = TimeF
    Render_ThreadControl(MyLoop)\ThreadID = CreateThread(@Render_Thread(),@Render_ThreadControl(Myloop)) 
  Next
  Time = ElapsedMilliseconds()
  For MyLoop = 1 To ThreadCount : WaitThread(Render_ThreadControl(MyLoop)\ThreadID) : Next
  Time = ElapsedMilliseconds() - Time : If Time < 0.1 : time = 0.1 : EndIf
  OnFrame + 1
  
  GLIP::Set_Array(#Gad_GL,@ImageArray_Main(),SizeX,SizeY,32)
  
  If Onframe > 1 And DynamicScale
    If Time > 12 : BadFrame + 1 : ElseIf Time < 7 : BadFrame - 1 : EndIf
    If Abs(BadFrame) = 4
      If BadFrame = 4 : Downscale * 1.2 : ElseIf Badframe = -4 : Downscale * 0.8 : EndIf
      If Downscale < MinScale : Downscale = MinScale : ElseIf Downscale > MaxScale : Downscale = MaxScale : EndIf
      SizeX = DesktopWidth(0) / Downscale : SizeY = SizeX / 2
      Dim ImageArray_Main.l(SizeY-1,SizeX-1)
      BadFrame = 0
    EndIf
  EndIf
EndProcedure
Procedure Init_Main()
  Protected MyLoop, StrideY.i = Round(SizeY / ThreadCount,#PB_Round_Up), Y = 0
  OpenWindow(#Window_Main,0,0,SizeX, SizeY,"",#PB_Window_Maximize|#PB_Window_BorderLess)
  OpenGLGadget(#Gad_GL, 0, 0,WindowWidth(#Window_Main),WindowHeight(#Window_Main), #PB_OpenGL_NoStencilBuffer|#PB_OpenGL_NoDepthBuffer|#PB_OpenGL_NoAccumulationBuffer)
  AddWindowTimer(0,0,1)
  
  ;/ Optimize parameters, as we're only rendering on 3/7ths of the screen
  StrideY.i = Round(((SizeY / 7)*3) / ThreadCount,#PB_Round_Up) :  Y = (SizeY / 7) * 2
  For MyLoop = 1 To ThreadCount
    Render_ThreadControl(Myloop)\Y_Start = Y : Y + StrideY
    Render_ThreadControl(Myloop)\Y_End = Y - 1
    If Render_ThreadControl(Myloop)\Y_End > SizeY-1 : Render_ThreadControl(Myloop)\Y_End = SizeY-1 : EndIf
  Next  
EndProcedure
Init_Main()

Repeat
  Event = WaitWindowEvent()
  If Event = #PB_Event_Timer : Render_Main() : EndIf
  If Event = #PB_Event_Gadget : If EventType() = #PB_EventType_LeftButtonUp : End : EndIf : EndIf
ForEver
Image
Last edited by pjay on Tue Sep 24, 2024 7:54 pm, edited 3 times in total.
User avatar
STARGÅTE
Addict
Addict
Posts: 2232
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: GLip - OpenGL Image Render

Post by STARGÅTE »

Looks nice. Thanks for sharing.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
Post Reply