Posted: 6th Apr 2022 22:32
I have been researching if it is possible to use a shader to "scratch off' a portion of a sprite's texture that the pointer touches (basically make that part of the sprite's texture to be transparent and reveal whatever that sprite is covering). It seems that it might be possible with a shader, but I have not located anything specific to how this might be done in OpenGL 2.0. I am guessing that the shader would use the area of the pointer sprite, which is set into position using the pointer. The shader possibly would use those coordinates to determine what UV coordinates on the texture should become transparent. Currently I use several sprites to cover another sprite and using the covering sprites' Hit Group to delete any of those sprites touched by the pointer's coordinates. This works fine but is a bit laborious to setup and probably uses more resources than is necessary. I appreciate any help in creating the shader code to do this if it is possible.

Edit: I added some commented code below to show how I do this using many "scratch off" sprites and show performance by moving all the sprites each frame:

+ Code Snippet
// Project: scratchoff 
// Created: 2022-04-07

// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "scratchoff" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 0, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts

DeviceWidth#=GetDeviceWidth()
DeviceHeight#=GetDeviceHeight()

Type blocks  //type for scratch off sprites
	ID as Integer  //sprite number
	grp as Integer //sprite hit group number
	PX as Float //sprite x position
	PY as Float //sprite y position
EndType
Global block as blocks[]

Global hitspr as Integer[4]	//array for pointer hitting scratch off sprites
Global reblkspr as Integer[4] //array for indexs of hit scratch off sprites

For cnt = 0 to 3  // set array values for hit sprite numbers and indexes
	reblkspr[cnt] = -1
	hitspr[cnt] = 0
Next cnt

Global rec1ImageID as Integer

DrawEllipse(4,4,4,4,MakeColor(255,255,255),MakeColor(255,255,255),1) //image for pointer sprite
CircleImageID=GetImage(0,0,8,8)

TSpriteID=CreateSprite(CircleImageID) //pointer sprite
SetSpritePosition(TSpriteID, 700, 700)
SetSpriteDepth(TSpriteID, 4)

ClearScreen()
DrawBox(0, 0, 8, 8, MakeColor(100,100,100), MakeColor(100,100,100), MakeColor(100,100,100), MakeColor(100,100,100), 1) //gray image for scratch off sprites
rec1ImageID=GetImage(0,0,8,8)

ClearScreen()
DrawBox(0, 0, 410, 256, MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), 1)  //blue image for covered sprite
rec2ImageID=GetImage(0,0,410,256)
ClearScreen()

Global cnts as Integer = 0 //count number block hits
Global PosX as Float = 700.0 //pointer x position
Global PosY as Float = 700.0 //pointer y position
Global x1 as Float = 8.0  //starting x for scratch off sprites
Global y1 as Float = 8.0 //starting y for scratch off sprites

settext()

for mspr = 0 to 2279 //create and position gray spites to cover blue sprite
	
	ID = CreateSprite(rec1ImageID)
	SetSpritePosition(ID, x1, y1)
	SetSpriteDepth(ID, 8)
	SetSpriteGroup(ID, 100)
	blocker as blocks
		blocker.ID = ID
		blocker.grp = 100
		blocker.PX = x1
		blocker.py = y1
	block.Insert(blocker)
	x1 = x1 + 7
	If x1 >= 426
		x1 = 8.0
		y1 = y1 + 7
	EndIf
Next mspr

smallmapc = CreateSprite(rec2ImageID)  //create covered blue sprite
SetSpritePosition(smallmapc, 10, 10)
SetSpriteDepth(smallmapc, 9)


do
	moveall() //move all remaining gray sprites to test performance
	moveback() //return all remaining gray sprites to test performance
	
	PosX=GetPointerX()
	PosY=GetPointerY()
	
	SetSpritePosition(TSpriteID, PosX, PosY)
	
	sprhit()
	
	If block.Length + 1 = 0 Then endapp() //end app
	
	SetTextString(numblt, "Number of Gray Sprites: " + Str(block.Length + 1) + " Performance increases as number of gray sprites decrease.")
	SetTextString(fpst, "Frames Per Second: " + Str(ScreenFPS()))
	
	Sync()
loop

Function sprhit()  // identify and count gray sprites hit by pointer's extended area
	
	cnts = 0 //counts number of gray sprites hit by pointer area
	hitspr[0] = GetSpriteHitGroup(100, PosX + 6, PosY) //extend pointer's hit area by + 2 X as sprite's normal hit is it's center 4X4 pixels
	If hitspr[0] > 1
		cnts = cnts + 1 
	EndIf
	hitspr[1] = GetSpriteHitGroup(100, PosX - 6, PosY) //extend pointer's hit area by - 2 X as sprite's normal hit is it's center 4X4 pixels
	If hitspr[1] > 1 
		cnts = cnts + 1 
	EndIf
	hitspr[2] = GetSpriteHitGroup(100, PosX, PosY + 6) //extend pointer's hit area by + 2 Y as sprite's normal hit is it's center 4X4 pixels
	If hitspr[2] > 1 
		cnts = cnts + 1 
	EndIf
	hitspr[3] = GetSpriteHitGroup(100, PosX, PosY - 6) //extend pointer's hit area by - 2 Y as sprite's normal hit is it's center 4X4 pixels
	If hitspr[3] > 1 
		cnts = cnts + 1 
	EndIf
	If hitspr[0] > 0 Or hitspr[1] > 0 Or hitspr[2] > 0 Or hitspr[3] > 0 //check which gray sprites are hit and delete them and then remove from gray sprite array
		delspr(cnts)
	EndIf

EndFunction

Function moveall() //move all remaining gray sprites to test performance
	
	For m = 0 to block.Length
		SetSpritePosition(block[m].ID, -2000, -2000)
	Next m

EndFunction

Function moveback() //return all remaining gray sprites to test performance

	For m = 0 to block.Length
		SetSpritePosition(block[m].ID, block[m].PX, block[m].PY)
	Next m

EndFunction

Function delspr(cntit as Integer) //sid1 as Integer, sid2 as Integer, sid3 as Integer, sid4 as Integer, cnt as Integer)
	
	For cntall = 0 to 3
		If GetSpriteExists(hitspr[cntall]) = 1
			If hitspr[cntall] > 1 Then DeleteSprite(hitspr[cntall])
		EndIf
	Next cntall

	For Index = block.Length to 0 Step -1 //determine index number of hit gray sprites
		For ctall = 0 to 3
			If hitspr[ctall] > 1
				If block[Index].ID = hitspr[ctall]
					reblkspr[ctall] = Index
					cntit = cntit - 1
				EndIF
			EndIf
		Next ctall
       If cntit = 0 Then Exit
    Next Index
    
    For a = 0 to 3  //sort gray sprite(s) to remove from array in descending order to ensure highest index number removed first
		For b = a to 3
			If reblkspr[a] <= reblkspr[b]
				temp = reblkspr[a]
				reblkspr[a] = reblkspr[b]
				reblkspr[b] = temp
			EndIf
		Next b
	Next a
	
    For cnton = 0 To 3 // remove hit sprites for array index
		If reblkspr[cnton] > -1
			block.Remove(reblkspr[cnton])
		EndIf
	Next cnton
    
    For cnton = 0 to 3
		reblkspr[cnton] = -1 //reset array data for hit gray sprite index
		hitspr[cnton] = 0 //reset array data for hit gray sprite numbers
	Next cnton
	
EndFunction

Function settext()  //create text for instructions and show data
	
	Global instructt as Integer //instructions text number
	Global numblt as Integer //show number gray sprites remaining
	Global fpst as Integer //FPS text number
	
	instructt = CreateText("Move pointer over gray area to 'scratch off' the gray sprites covering a blue sprite. App will end shortly after the last gray sprite has been removed.")
	SetTextSize(instructt, 30)
	SetTextPosition(instructt, 550, 10)
	SetTextMaxWidth(instructt, 400)
	
	numblt = CreateText("Number of Gray Sprites: " + Str(block.Length + 1) + " .Performance increases as number of gray sprites decrease.")
	SetTextSize(numblt, 30)
	SetTextPosition(numblt, 550, 190)
	SetTextMaxWidth(numblt, 400)
	
	fpst = CreateText("Frames Per Second: " + Str(ScreenFPS()))
	SetTextSize(fpst, 30)
	SetTextPosition(fpst, 550, 320)

EndFunction

Function endapp() //end app
	
	Local endt as Float
	
	SetTextString(numblt, "Number of Gray Sprites: " + Str(block.Length + 1) + " Performance increases as number of gray sprites decrease.")
	SetTextString(fpst, "Frames Per Second: " + Str(ScreenFPS()))
	
	endt = Timer()

	Repeat
		Sync()
	Until endt + 5 < Timer() //app stops in 5 seconds
	
	DeleteAllText()
	DeleteAllSprites()
	DeleteAllImages()
	
	End

EndFunction
Posted: 7th Apr 2022 21:42
I made it with shaders but it was so slow.

Using memblocks would be nice;

bon apetit;


+ Code Snippet
// Project: picture scratch 
// Created: 2022-04-07

// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "picture scratch" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 30, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts


Global _MAIN_IMAGE_ As Integer, _MAIN_SPRITE_ As Integer
Global _MAIN_FRAME_ As Integer
Global _IMAGE_LEFT_POS_ As Float, _IMAGE_TOP_POS_ As Float


_INIT_()


Local NewImage As Integer, X As Integer, Y As Integer
Do
    If GetPointerState()
		X = Floor(GetPointerX() - _IMAGE_LEFT_POS_)
		Y = Floor(GetPointerY() - _IMAGE_TOP_POS_)
		NewImage = _FILL_CIRCLE_IMAGE_(_MAIN_FRAME_, X, Y , 8, 0xFF000000)
		SetImageTransparentColor(NewImage, 0, 0, 0)
		SetSpriteImage(_MAIN_SPRITE_, NewImage)
		DeleteImage(_MAIN_IMAGE_)
		_MAIN_IMAGE_ = NewImage
	EndIf
    Print( ScreenFPS() )
    Sync()
Loop





Function _FILL_CIRCLE_IMAGE_(_O_ As Integer, _X_ As Integer, _Y_ As Integer, _R_ As Integer, _C_ As Integer) //Yes we can!
	//we will use brehansam's circle algorithm
	//this algorithm selects pixels at eight octans
	//we simple draw horizontal lines between those points
	
	
	/*
	     A
	    B*B
	   C***C
	   C***C
	    C*C
	     C
	*/
	
	Local Ax As Integer, Ay As Integer, E As Integer, L As Integer
	Local XStart As Integer, XStop As Integer, LY As Integer, Offset As Integer
	Local XLim As Integer, YLim As Integer, Pitch As Integer
    
    
    Pitch = GetMemblockInt(_O_, 0)
    XLim = Pitch - 1
    YLim = GetMemblockInt(_O_, 4) - 1
    
    
    Ax = _R_
    Ay = 0
    E = 0
    
    
    Do
		If Ax < Ay Then Exit
		XStart = _X_ - Ax
		XStop = _X_ + Ax
		If XStart < 0 Then XStart = 0
		If XStop > XLim Then XStop = XLim
		
		
		//drawline(xtart, Ly, xstop, Ly, _C_)
		Ly = _Y_ + Ay
		If (Ly >= 0) And (Ly <= YLim)
			Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
			For L = XStart To XStop
				SetMemblockInt(_O_, Offset, _C_)
				Offset = Offset + 4
			Next
		EndIf

		Ly = _Y_ - Ay
		If (Ly >= 0) And (Ly <= YLim)
			Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
			For L = XStart To XStop
				SetMemblockInt(_O_, Offset, _C_)
				Offset = Offset + 4
			Next
		EndIf

		
		XStart = _X_ - Ay
		XStop = _X_ + Ay
		If XStart < 0 Then XStart = 0
		If XStop > XLim Then XStop = XLim
		Ly = _Y_ + Ax
		If (Ly >= 0) And (Ly <= YLim)
			Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
			For L = XStart To XStop
				SetMemblockInt(_O_, Offset, _C_)
				Offset = Offset + 4
			Next
		EndIf

        
		Ly = _Y_ - Ax
		If (Ly >= 0) And (Ly <= YLim)
			Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
			For L = XStart To XStop
				SetMemblockInt(_O_, Offset, _C_)
				Offset = Offset + 4
			Next
		EndIf
        
        
        If E <= 0
            Ay = Ay + 1
            E = E + Ay * 2 + 1
        EndIf
        
        If E > 0
            Ax = Ax - 1
            E = E - Ax * 2 + 1
        EndIf
    Loop	
	
	X = CreateImageFromMemblock(_O_)
	
EndFunction X



Function _INIT_()
	_MAIN_IMAGE_ = _CREATE_RANDOM_IMAGE_(256, 256)  //use LoadImage("image.png") here, "no-media" pollicy is for tutorial and understanding "draw_dot_image"
	SetImageTransparentColor(_MAIN_IMAGE_, 0, 0, 0)
	_MAIN_SPRITE_ = CreateSprite(_MAIN_IMAGE_)
	SetSpriteTransparency(_MAIN_SPRITE_, 1)
	_IMAGE_LEFT_POS_ = 123
	_IMAGE_TOP_POS_ = 99
	SetSpritePosition(_MAIN_SPRITE_, _IMAGE_LEFT_POS_, _IMAGE_TOP_POS_)
	_MAIN_FRAME_ = CreateMemblockFromImage(_MAIN_IMAGE_)
EndFunction


Function _DRAW_DOT_IMAGE_(_O_ As Integer, _X_ As Integer, _Y_ As Integer, _C_ As Integer) //It could be r, g, b as well
	//Since this function is not used in this project
	//It shows how to plot a pixel in a given WxH integer surface
	Local W As Integer, H As Integer, Offset As Integer
	If _X_ < 0 Then ExitFunction 0
	If _Y_ < 0 Then ExitFunction 0
	W = GetMemblockInt(_O_, 0)
	If _X_ >= W Then ExitFunction 0
	H = GetMemblockInt(_O_, 4)
	If _Y_ >= H Then ExitFunction 0
	
	Offset = (3 + _X_ + (_Y_ * W)) << 2
	SetMemblockInt(_O_, Offset, _C_)
	
	W = CreateImageFromMemblock(_O_)
	
EndFunction W


Function _CREATE_RANDOM_IMAGE_(_W_ As Integer, _H_ As Integer)
	Local O As Integer
	Local X As Integer, Y As Integer, Offset As Integer
	Local R As Integer, G As Integer, B As Integer
	O = CreateMemblock(12 + _W_ * _H_ * 4)
	SetMemblockInt(O, 0, _W_)
	SetMemblockInt(O, 4, _H_)
	SetMemblockInt(O, 8, 32)
	
	Offset = 12
	For Y = 0 To _H_ - 1
		For X = 0 To _W_ - 1
			R = X ~~ Y  //x xor y
			G = Y ~~ X  //y xor x
			B = Y && X  //y And x
			B = B << 16 //blue channel, notice A; B; G ;R
			B = B || 0xFF000000 //alpha channel
			B = B || (G << 8)
			B = B || R
			SetMemblockInt(O, Offset, B)
			Offset = Offset + 4
		Next
	Next
	
	X = CreateImageFromMemblock(O)
	Deletememblock(O)
EndFunction X
Posted: 7th Apr 2022 22:15
And here is a literally image scratcher;

+ Code Snippet
// Project: picture scratch 
// Created: 2022-04-07
 
// show all errors
SetErrorMode(2)
 
// set window properties
SetWindowTitle( "picture scratch" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window
 
// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 30, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts
 
 
Global _MAIN_IMAGE_ As Integer, _MAIN_SPRITE_ As Integer
Global _MAIN_FRAME_ As Integer
Global _IMAGE_LEFT_POS_ As Float, _IMAGE_TOP_POS_ As Float
 
 
_INIT_()
 
 
Local NewImage As Integer, X As Integer, Y As Integer
Do
    If GetPointerState()
        X = Floor(GetPointerX() - _IMAGE_LEFT_POS_)
        Y = Floor(GetPointerY() - _IMAGE_TOP_POS_)
        NewImage = _FILL_CIRCLE_IMAGE_(_MAIN_FRAME_, X, Y , 13, 0xFF000000)
        SetImageTransparentColor(NewImage, 0, 0, 0)
        SetSpriteImage(_MAIN_SPRITE_, NewImage)
        DeleteImage(_MAIN_IMAGE_)
        _MAIN_IMAGE_ = NewImage
    EndIf
    Print( ScreenFPS() )
    Sync()
Loop
 


Function _SCRATCH_IMAGE_(_O_ As Integer, _X0_ As Integer, _X1_ As Integer, _Y0_ As Integer, _A_ As Integer)
	Local XLim As Integer, YLim As Integer, Pitch As Integer, L As Integer, Offset As Integer, C As Integer
	Local A As Integer, R As Integer, G As Integer, B As Integer
    Pitch = GetMemblockInt(_O_, 0)
    XLim = Pitch - 1
    YLim = GetMemblockInt(_O_, 4) - 1
    If (_Y0_ < 0) Or (_Y0_ > YLim) Then ExitFunction
    
    If _X0_ < 0 Then _X0_ = 0
    If _X1_ > XLim Then _X1_ = XLim
    Offset = (3 + (_X0_ + _Y0_ * Pitch)) << 2  //12 + (X + Y * W) * 4
    For L = _X0_ To _X1_
		C = GetMemblockInt(_O_, Offset)
		A = (C >> 24)
		A = A - _A_
		If A < 0 Then A = 0


		B = (C >> 16) && 255
		B = B - _A_
		If B < 0 Then B = 0

		G = (C >> 8) && 255
		G = G - _A_
		If G < 0 Then G = 0
		
		R = C && 255
		R = R - _A_
		If R < 0 Then R = 0
		
		
		
		C = (A << 24) || (B << 16) || (G << 8) || R
		
		SetMemblockInt(_O_, Offset, C)
        Offset = Offset + 4
    Next
EndFunction


 
Function _FILL_CIRCLE_IMAGE_(_O_ As Integer, _X_ As Integer, _Y_ As Integer, _R_ As Integer, _C_ As Integer) //Yes we can!
    //we will use brehansam's circle algorithm
    //this algorithm selects pixels at eight octans
    //we simple draw horizontal lines between those points
     
     
    /*
         A
        B*B
       C***C
       C***C
        C*C
         C
    */
     
    Local Ax As Integer, Ay As Integer, E As Integer, L As Integer
    Local XStart As Integer, XStop As Integer, LY As Integer, Offset As Integer
    Local XLim As Integer, YLim As Integer, Pitch As Integer
     
     
    Pitch = GetMemblockInt(_O_, 0)
    XLim = Pitch - 1
    YLim = GetMemblockInt(_O_, 4) - 1
     
     
    Ax = _R_
    Ay = 0
    E = 0
     
     
    Do
        If Ax < Ay Then Exit
        XStart = _X_ - Ax
        XStop = _X_ + Ax
        Ly = _Y_ + Ay
        _SCRATCH_IMAGE_(_O_, XStart, XStop, Ly, 32)
        Ly = _Y_ - Ay
        _SCRATCH_IMAGE_(_O_, XStart, XStop, Ly, 32)
        
        
        XStart = _X_ - Ay
        XStop = _X_ + Ay
        Ly = _Y_ + Ax
        _SCRATCH_IMAGE_(_O_, XStart, XStop, Ly, 32)
        Ly = _Y_ - Ax
        _SCRATCH_IMAGE_(_O_, XStart, XStop, Ly, 32)
        

         
         
        If E <= 0
            Ay = Ay + 1
            E = E + Ay * 2 + 1
        EndIf
         
        If E > 0
            Ax = Ax - 1
            E = E - Ax * 2 + 1
        EndIf
    Loop   
     
    X = CreateImageFromMemblock(_O_)
     
EndFunction X
 
 
 
Function _INIT_()
    _MAIN_IMAGE_ = _CREATE_RANDOM_IMAGE_(256, 256)  //use LoadImage("image.png") here, "no-media" pollicy is for tutorial and understanding "draw_dot_image"
    SetImageTransparentColor(_MAIN_IMAGE_, 0, 0, 0)
    _MAIN_SPRITE_ = CreateSprite(_MAIN_IMAGE_)
    SetSpriteTransparency(_MAIN_SPRITE_, 1)
    _IMAGE_LEFT_POS_ = 123
    _IMAGE_TOP_POS_ = 99
    SetSpritePosition(_MAIN_SPRITE_, _IMAGE_LEFT_POS_, _IMAGE_TOP_POS_)
    _MAIN_FRAME_ = CreateMemblockFromImage(_MAIN_IMAGE_)
EndFunction
 
 
Function _DRAW_DOT_IMAGE_(_O_ As Integer, _X_ As Integer, _Y_ As Integer, _C_ As Integer) //It could be r, g, b as well
    //Since this function is not used in this project
    //It shows how to plot a pixel in a given WxH integer surface
    Local W As Integer, H As Integer, Offset As Integer
    If _X_ < 0 Then ExitFunction 0
    If _Y_ < 0 Then ExitFunction 0
    W = GetMemblockInt(_O_, 0)
    If _X_ >= W Then ExitFunction 0
    H = GetMemblockInt(_O_, 4)
    If _Y_ >= H Then ExitFunction 0
     
    Offset = (3 + _X_ + (_Y_ * W)) << 2
    SetMemblockInt(_O_, Offset, _C_)
     
    W = CreateImageFromMemblock(_O_)
     
EndFunction W
 
 
Function _CREATE_RANDOM_IMAGE_(_W_ As Integer, _H_ As Integer)
    Local O As Integer
    Local X As Integer, Y As Integer, Offset As Integer
    Local R As Integer, G As Integer, B As Integer
    O = CreateMemblock(12 + _W_ * _H_ * 4)
    SetMemblockInt(O, 0, _W_)
    SetMemblockInt(O, 4, _H_)
    SetMemblockInt(O, 8, 32)
     
    Offset = 12
    For Y = 0 To _H_ - 1
        For X = 0 To _W_ - 1
            R = X ~~ Y  //x xor y
            G = Y ~~ X  //y xor x
            B = Y && X  //y And x
            B = B << 16 //blue channel, notice A; B; G ;R
            B = B || 0xFF000000 //alpha channel
            B = B || (G << 8)
            B = B || R
            SetMemblockInt(O, Offset, B)
            Offset = Offset + 4
        Next
    Next
     
    X = CreateImageFromMemblock(O)
    Deletememblock(O)
EndFunction X
Posted: 8th Apr 2022 0:06
Arch-OK,

Thank you very much. I had not thought of using memblocls images. It works very nicely, but still has performance issues, but better than using many sprites. You mentioned using a shader and that it was slow. Would you mind providing the shader, so that I can learn from it? Again, thanks for your help.
Posted: 8th Apr 2022 4:05
you could probably modify janbo's burn shader to suit your needs.

ie, get rid of the auto-burn and concentrate the 0 alpha on the "scratch me" sprite around wherever the mouse is?

meanwhile, using your initial method, you're basically keeping track of what's being removed with the sprite count. i'm not sure how to go about that with the shader. keep track of a grid (boolean-ish array) with much fewer than ~2400 areas if you DO need to track it. otherwise, simply apply the burn shader nearly "as is"?

otherwise, i know very little about shaders but hope this helps you along.
Posted: 8th Apr 2022 17:59
Virtual Nomad

Thank you. Like you, I have very little experience with shaders. I agree that this will likely require some array in the shader, but based on the size of the image and the radius of the scratching area, this will probably require an array index of several thousand, which would end up being a performance hit. When I changed the burn.ps file a bit, I was able to get the shader to only burn the area around the match, but as soon as I moved the match the new area would burn, but the previous area would return, so the shader was forgetting what had already been burned. That is why an array is likely needed to use with a for/next loop in the shader. Much to learn yet; however, when I did place a simple for/next loop in the shader (just counts up from 0 to 200,000, I did not see a performance issue in the app. Will try to learn how to send an array of floor x and y positions based on radius of scratching sprite to the shader and then have the shader search through increased index number to ensure that only new indexes (arrays of different x and y values) are only inserted into the shader array of positions. Then it must go through the entire length of the array to burn off positions already scratched. Thanks for any help others could provide in coding this into the shader.
Posted: 8th Apr 2022 21:20
Here is my code to create a surface and draw there just by touching.
It creates a surface with sprites size of 16x16 pixels.
When pointer touches, the code finds which sprite is at the pointers position then the program finds which pixel of that sprite is at the pointer's position and sets the color for that pixel.
Why subdivide?
because, arrays in shaders have limits of 16384 bytes (guessing) -> uniform vec4 pixeldata[1024]; -> 16 x 1024...

as I remember the code runs good but it was slow for big areas.

a small area example;



anyways, I hope you can modify it to your liking...

surface.vs
+ Code Snippet
attribute vec4 position;
attribute vec4 color;
attribute vec2 uv;
varying mediump vec2 uvVarying;
varying mediump vec2 posVarying;
uniform mat4 agk_Ortho;
void main() { 
	gl_Position = agk_Ortho * position;
	posVarying = position.xy;
	uvVarying = uv;
}



surface.ps
+ Code Snippet
uniform vec4 surface[256];
varying mediump vec2 uvVarying;

void main()
{
int x, y, p;
x = int(uvVarying.x * 16.0);
y = int(uvVarying.y * 16.0);
p = x + y * 16;
gl_FragColor = surface[p];
}



agk code
+ Code Snippet
// Project: Shader Surface 
// Created: 2022-02-14

// show all errors
SetRawWritePath(GetReadPath())
SetErrorMode(2)
#option_explicit



// set window properties
SetWindowTitle( "Shader Surface" )
SetWindowSize( 1920, 1080, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1920, 1080 ) // doesn't have to match the window
SetOrientationAllowed( 0, 0, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 0, 1 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts




Global _VIEW_LEFT_ As Float, _VIEW_TOP_ As Float, _VIEW_WIDTH_ As Float, _VIEW_HEIGHT_ As Float, _VIEW_RIGHT_ As Float, _VIEW_BOTTOM_ As Float
Global _SURFACE_WIDTH_ As Integer, _SURFACE_HEIGHT_ As Integer, _SURFACE_LINE_LEN_ As Integer
Global _SURFACE_IMAGE_ As Integer, _X_LIM_ As Integer, _Y_LIM_ As Integer
Global _SURFACE_SPRITES_ As Integer[16384], _SURFACE_SHADERS_ As Integer[16384]

#Constant _SPRITE_SIZE_ 16

Function _INIT_(_W_ As Integer, _H_ As Integer)
	Local Mem As Integer, Offset As Integer, i As Integer, K As Integer, X As Integer, Y As Integer, Z As Integer
	
	_X_LIM_ = _W_ - 1
	_Y_LIM_ = _H_ - 1
	_SURFACE_WIDTH_ = _W_ * _SPRITE_SIZE_
	_SURFACE_HEIGHT_ = _H_ * _SPRITE_SIZE_
	_SURFACE_LINE_LEN_ = _W_
	_VIEW_LEFT_ = 0
	_VIEW_TOP_ = 0
	_VIEW_WIDTH_ = _SURFACE_WIDTH_
	_VIEW_HEIGHT_ = _SURFACE_HEIGHT_
	_VIEW_RIGHT_ = _VIEW_LEFT_ + _VIEW_WIDTH_ - 1
	_VIEW_BOTTOM_ = _VIEW_TOP_ + _VIEW_HEIGHT_
	
	Mem = CreateMemblock(12 + (_SPRITE_SIZE_ * _SPRITE_SIZE_) * 4)
	Offset = 0
	SetMemblockInt(Mem, Offset, _SPRITE_SIZE_) :Offset = Offset + 4
	SetMemblockInt(Mem, Offset, _SPRITE_SIZE_) :Offset = Offset + 4
	SetMemblockInt(Mem, Offset, 32) :Offset = Offset + 4
	For Y = 0 To _SPRITE_SIZE_ - 1
		For X = 0 To _SPRITE_SIZE_ - 1
			SetMemblockInt(Mem, Offset, 0xFFFFFFFF) :Offset = Offset + 4
		Next
	Next
	_SURFACE_IMAGE_ = CreateImageFromMemblock(Mem)
	DeleteMemblock(Mem)
	i = 0
	For Y = 0 To _H_ - 1
		For X = 0 To _W_ - 1
			Z = CreateSprite(_SURFACE_IMAGE_)
			SetSpriteTransparency(Z, 0)
			SetSpritePosition(Z, _VIEW_LEFT_ + X * _SPRITE_SIZE_, _VIEW_TOP_ + Y * _SPRITE_SIZE_)
			K = LoadShader("surface.vs", "surface.ps")
			//K = LoadSpriteShader("surface.ps")
			SetSpriteShader(Z, K)
			_SURFACE_SPRITES_[i] = Z
			_SURFACE_SHADERS_[i] = K
			i = i + 1
			
			/*
			Local kk As Integer, ll As Integer
			Offset = 0
			For kk = 0 To 15
				For ll = 0 To 15
					SetShaderConstantArrayByName(K, "surface", Offset, 255 - kk * 16, ll * 16, 255, 255)
					Offset = Offset + 1
				Next
			Next
			*/
		Next
	Next
EndFunction


_INIT_(20, 10)

Local r As Float, g As Float, b As Float

do
    

    Print( ScreenFPS() )
    
    If GetPointerState()
		_DRAW_DOT_(GetPointerX() - _VIEW_LEFT_, GetPointerY() - _VIEW_TOP_, 0, 0, 255)
	EndIf
    Sync()
loop


Function _DRAW_DOT_(_X_ As Integer, _Y_ As Integer, _R_ As Float, _G_ As Float, _B_ As Float)
	Local Segment As Integer, X As Integer, Y As Integer, Offset As Integer
	X = Floor(_X_/_SPRITE_SIZE_)
	If X < 0 Then ExitFunction
	If X > _X_LIM_ Then ExitFunction
	Y = Floor(_Y_/_SPRITE_SIZE_)
	If Y < 0 Then ExitFunction
	If Y > _Y_LIM_ Then ExitFunction
	Segment = X + Y * _SURFACE_LINE_LEN_
	
	
	X = _X_ - X * _SPRITE_SIZE_
	Y = _Y_ - Y * _SPRITE_SIZE_
	Offset = X + Y * _SPRITE_SIZE_
	SetShaderConstantArrayByName(_SURFACE_SHADERS_[Segment], "surface", Offset, _R_, _G_, _B_, 255)
EndFunction
Posted: 9th Apr 2022 4:34
Arch-Ok,

Again, thanks. Interesting take with much to study. Like you noticed it does seem to have a slight performance hit, even when not scratching. I think if I can somehow use your segment/offset idea with Janbo's burn shader, it might work very well, something like just increasing the dot size you use. Hopefully I'll be able to work it out, but I think it will take a while as I truly do not understand shaders very well and what they can and cannot do. I think the scratch idea would work well to reveal the hidden areas of a map in the HUD as a player searches through a dungeon and other similar uses.
Posted: 9th Apr 2022 15:05
You are welcome joseph. I am also not into the shaders that much but the code with the memblocks runs much faster than the code with shaders on my pc. (the codes I shared on this topic only. The shaders are always faster for common/general/known purposes!)
Using mask sprite as (W/2)x(H/2) sizes on an other sprite (sprite to be revealed) with sizes of WxH would be faster with resizing the sprite to WxH.

Anyways, I'm on a project may be after a month it will be released and it opens areas on an sprite and I am using memblocks.
Creating a dll for faster memblock usage is possible, I mean without checking if a memblock exists, and if the offset limit exceeds the memblock size e.t.c.
I hope you find a way and share with us, good luck bud
Posted: 9th Apr 2022 23:55
Have not had much success yet. However, I at least am able to change the color of the individual segments of a segmented sprite (21 by 13). Code and shaders below (quickly put together, but mainly works. Two things I am desperately trying to understand are the following:

1. The arrayIndex part of the SetShaderConstantArrayByName (arrayIndex - The index of the element within the array to modify) command. It seems that the command sends the specific index number to the shader, but I am at a loss as to how to use that number in the shader.
2. I am having great difficulty in getting the shader to change the alpha of the sprites texture for each segment. I can get the color to change based on the x(red) and y(green) values sent to the shader, but have not determined how to get the alpha vale to change to zero.

The current code and shaders do not (in present form) take a performance hit, so it seems to be in the right direction. If anyone is able to provide guidance and information to the two problems, please help.

AGK Code: (no media needed)
+ Code Snippet
// Project: reveal 
// Created: 2022-04-08

// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "reveal" )
SetWindowSize( 1280, 800, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window


// set display properties
SetVirtualResolution(1280,800)
SetOrientationAllowed( 1, 1, 1, 1 )
SetPrintColor(0,0,0)
SetPrintSize(2)
SetClearColor(10,10,10)

SetRawMouseVisible(0)

SetPrintSize(50)
SetPrintColor(255, 255, 255)

SetSyncRate(0,0)

If GetDeviceBaseName() = "windows"
	SetRawWritePath(GetReadPath()) //set save path as media folder
EndIf

DeviceWidth#=GetDeviceWidth()
DeviceHeight#=GetDeviceHeight()

Global rechide as Integer //sprite number for hidden rectangle
Global recscratch as integer //sprite number for scratching rectangle

Global rec1ImageID as Integer
Global rec2ImageID as Integer
Global DiscardImageID as Integer

DrawEllipse(4,4,4,4,MakeColor(255,255,255),MakeColor(255,255,255),1) //image for pointer sprite
CircleImageID=GetImage(0,0,8,8)

TSpriteID=CreateSprite(CircleImageID) //pointer sprite
SetSpritePosition(TSpriteID, 700, 700)
SetSpriteDepth(TSpriteID, 4)
SetSpriteOffSet(TSpriteID, 4, 4)

ClearScreen()
DrawBox(0, 0, 420, 260, MakeColor(0,0,0), MakeColor(0,0,0), MakeColor(0,0,0), MakeColor(0,0,0), 1) //
DiscardImageID=GetImage(0, 0, 420, 260) 
ClearScreen()

DrawBox(0, 0, 420, 260, MakeColor(100,100,100), MakeColor(100,100,100), MakeColor(100,100,100), MakeColor(100,100,100), 1) //gray image for scratch off sprites
rec1ImageID=GetImage(0, 0, 420, 260) //410,256)
SaveImage(rec1ImageID, "image1.png")
DeleteImage(rec1ImageID)

ClearScreen()
DrawBox(0, 0, 410, 256, MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), 1)  //blue image for covered sprite
rec2ImageID=GetImage(0, 0, 420, 260)
ClearScreen()

rechide = CreateSprite(rec2ImageID)  //create covered blue sprite
SetSpritePosition(rechide, 50, 50)
SetSpriteDepth(rechide, 9)

//DiscardImageID=loadimage("noise.png")

rec1ImageID = LoadImage("image1.png")
recscratch = CreateSprite(rec1ImageID)  //create gray scratch sprite
//SetSpriteAdditionalImage(recscratch,DiscardImageID,1)
SetSpritePosition(recscratch, 50, 50)
SetSpriteSize(recscratch, 420, 260)
SetSpriteDepth(recscratch, 8)

ShaderID=LoadShader("Vertex.vs", "reveal.ps" )
SetSpriteShader(recscratch,ShaderID)

Type reveals
	x as Integer //floor x position
	y as Integer //floor y position
	xs as Float	//shader x position
	ys as Float //shader y position
	on as Integer //on = 1 if position has been revealed
	ar as integer //index number 
EndType
Global reveal as reveals[]

Global okay as Integer //check if position array already exists
Global seg as Integer
Global x# as Float
Global y# as Float
Global px as Integer
Global mx as Integer
Global py as Integer
Global my as Integer

do
	PointerX#=GetPointerX()
	PointerY#=GetPointerY()
	
	SetSpritePositionByOffset(TSpriteID,PointerX#,PointerY#)
 	
	If GetSpriteHitTest(recscratch,PointerX#,PointerY#)=1 And okay = 0 //And Trunc(dist) > 20
		seg = Floor((PointerX# - 50)/20) + (Floor((PointerY# - 50)/20)) * 21
		For chkar = 0 To reveal.Length
			If reveal[chkar].on = seg
				okay = 1
				Exit
			EndIf
		Next chkar
	
		If BurnSwitch=0 And okay = 0 And GetSpriteHitTest(recscratch,PointerX#,PointerY#)=1
			px = Floor(PointerX#) - 50
			mx = Mod(px, 20)
			px = px - mx //+ 10
			py = Floor(PointerY#) - 50
			my = Mod(py, 20)
			py = py - my //+ 10

			//BurnSwitch=1
			x# = px / GetSpriteWidth(recscratch) //((PointerX#+50)/GetSpriteWidth(recscratch)) //- 0.125
			y# = py / GetSpriteHeight(recscratch) //((PointerY#+50)/GetSpriteHeight(recscratch)) //- 0.2
			thisreveal as reveals
				thisreveal.x = Floor(PointerX#) //Floor(100 * x#)
				thisreveal.y = Floor(PointerY#) //Floor(100 * y#)
				thisreveal.xs = x#
				thisreveal.ys = y#
				thisreveal.on = seg
				thisreveal.ar = reveal.Length + 1
			reveal.Insert(thisreveal)	
			//For chk = 0 To reveal.Length
				//If reveal[chk].x <= Floor(PointerX#) + 5 And reveal[chk].x >= Floor(PointerX#) - 5 //Floor(100 * x#)
					//For chk2 = 0 to reveal.Length
						//If reveal[chk].y <= Floor(PointerY#) + 5 And reveal[chk].y >= Floor(PointerY#) - 5 //reveal[chk].y = Floor(PointerY#) //Floor(100 * y#)
							//okay = 1
							//Exit
						//EndIf
					//Next chk2
				//EndIf	
			//Next chk
			setShaderConstantArrayByName(ShaderID, "center", seg, reveal[reveal.Length].xs, reveal[reveal.length].ys, 0.1, 0.1) //x#,y#,0.1,0)
				//setShaderConstantByName(ShaderID, "BurnParams",1.0, 1.0, 1.0,0 ) //4.0, 0.1, 0.1
			Time# = -0.4 //Time#=-0.4
			disx = PointerX#
			disy = PointerY#
			timestep1 = Timer()
		Endif
	EndIf
	okay = 0
		
	if getpointerpressed()=1 //used to just reset reveal array
		BurnSwitch=0
		//setShaderConstantByName(ShaderID, "time",-99.0,0,0,0)
		disx = PointerX#
		disy = PointerY#
		For lose = reveal.Length to 0 Step -1
			reveal.Remove(lose)
		Next lose
	endif
	

    Print( ScreenFPS() )
    print("x#: " + Str(x#) + "  px: " + Str(px) + "  seg: " + Str(seg))
    Print(Str(reveal.Length))
    
    Sync()
loop


Shader VS code:
+ Code Snippet
attribute vec4 position;
attribute vec4 color;
attribute vec2 uv;

varying vec2 uvVarying;
varying vec4 colorVarying;
varying vec2 posVarying;

uniform mat4 agk_Ortho;


void main()
{ 
	gl_Position = agk_Ortho * position;
	posVarying = position.xy;
	uvVarying = uv;
	colorVarying = color;
}


Shade PS Code:
+ Code Snippet
uniform vec4 center[512]; // Mouse position
uniform sampler2D texture0;
uniform sampler2D texture1;

varying mediump vec2 uvVarying;
varying vec2 posVarying;

uniform vec2 agk_resolution;

void main()
{
	vec4 ColorTexture = texture2D(texture0, uvVarying); //vec4 color = texture2D(texture0, uvVarying).rgba;
	vec4 color = texture2D(texture0, uvVarying).rgba;
	vec2 BurnParams = uvVarying;
	BurnParams.x = 1.0;
	color.a = 0.1;
	float xi, yi;
	int x, y;
	int p;
	xi = 0.0;
	xi = xi + (uvVarying.x * 420.0); 
	x = int(xi/20.0);
	yi = 0.0; //.2;
	yi = yi + (uvVarying.y * 260.0);
	y = int(yi/20.0);
	p = x + (y*21);
	float Alpha = color.a; // - ( / dist); //1.0 - ((DiscardTexture.r) / dist); //diff)/diff;
	color = clamp(color,0.0,1.0);
	gl_FragColor = vec4(center[p].rg, color.b, Alpha); //gl_FragColor = vec4(ColorTexture.rgb, Alpha); // gl_FragColor = center[p]; // 
}
Posted: 10th Apr 2022 1:17
Yes, shaders are much more faster. The shader code is really small and easy. The init of the agk code is a little bit complex. Loading media makes some functions obsolote



scratch.vs
+ Code Snippet
attribute vec4 position;
attribute vec4 color;
attribute vec2 uv;

varying vec2 uvVarying;
varying vec4 colorVarying;
varying vec2 posVarying;

uniform mat4 agk_Ortho;


void main()
{ 
	gl_Position = agk_Ortho * position;
	posVarying = position.xy;
	uvVarying = uv;
	colorVarying = color;
}


scratch.ps
+ Code Snippet
varying vec2 uvVarying;
varying vec2 posVarying;

uniform sampler2D texture0;
uniform vec2 agk_resolution;
uniform float alpha_mask [1024];

void main()
{
	int x, y, p; 
	x = int(uvVarying.x * 32.0);
	y = int(uvVarying.y * 32.0);
	p = (x + y * 32);
	float alpha = alpha_mask[p];

	vec4 texturecolor = texture2D(texture0, uvVarying);
	gl_FragColor = vec4(texturecolor.rgb, alpha);
}



agk code;
+ Code Snippet
// Project: Shader Scratch 
// Created: 2022-04-08

// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "Shader Scratch" )
SetWindowSize( 1920, 1080, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1920, 1080 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 0, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts


#Constant _SPRITE_SIZE_BITS_ 5
#Constant _MAX_SPRITES_ 2048
Global _IMAGE_LIST_ As Integer[_MAX_SPRITES_]
Global _SPRITE_LIST_ As Integer[_MAX_SPRITES_]
Global _SHADER_LIST_ As Integer[_MAX_SPRITES_]
Global _ALPHA_LIST_ As Float[_MAX_SPRITES_, 1024]  //If you change the spritesizebits, you must change here also!
Global _SPRITE_COUNT_X_ As Integer
Global _SPRITE_COUNT_Y_ As Integer
Global _VIEW_LEFT_ As Float, _VIEW_TOP_ As Float
Global _TOTAL_SPRITES_ As Integer
Global _CURSOR_IMAGE_ As Float[1024, 1024]
Global _CURSOR_WIDTH_ As Integer, _CURSOR_HEIGHT_ As Integer
Global _CURSOR_MID_X_ As Float, _CURSOR_MID_Y_ As Float
Global _BACK_SPRITE_ As Integer



_INIT_("test.png", "cursor.png", 300, 300)  //image to be scratched, the image of the cursor, left and top positions


Do
	If GetPointerState() Then _SCRATCH_(GetPointerX(), GetPointerY())
    Print( ScreenFPS() )
    Sync()
Loop


Function _SCRATCH_(_X_ As Float, _Y_ As Float)
	Local X As Integer, Y As Integer
	Local XStart As Float, dY As Float, dX As Float
	
	XStart = _X_ - _CURSOR_MID_X_
	dY = _Y_ - _CURSOR_MID_Y_
	
	For Y = 0 To _CURSOR_HEIGHT_ - 1
		dX = XStart
		For X = 0 To _CURSOR_WIDTH_ - 1
			_SET_PIXELS_ALPHA_(dX, dY, _CURSOR_IMAGE_[X, Y])
			dX = dX + 1
		Next
		dY = dY + 1
	Next
	
EndFunction

Function _SET_PIXELS_ALPHA_(_X_ As Float, _Y_ As Float, _A_ As Float)
	Local X As Integer, Y As Integer, Sy As Integer
	Local EntityNo As Integer, ShaderIndex As Integer, PixelIndex As Integer
	Local A As Float
	X = Floor(_X_ - _VIEW_LEFT_)
	If X < 0 Then ExitFunction
	Y = Floor(_Y_ - _VIEW_TOP_)
	If Y < 0 Then ExitFunction
	
	
	Sx = X >> _SPRITE_SIZE_BITS_
	If Sx >= _SPRITE_COUNT_X_ Then ExitFunction
	Sy = Y >> _SPRITE_SIZE_BITS_
	If Sy >= _SPRITE_COUNT_Y_ Then ExitFunction
	EntityNo = Sx + Sy * _SPRITE_COUNT_X_
	ShaderIndex = _SHADER_LIST_[EntityNo]
	X = X - (Sx << _SPRITE_SIZE_BITS_)
	Y = Y - (Sy << _SPRITE_SIZE_BITS_)
	PixelIndex = X + (Y << _SPRITE_SIZE_BITS_)
	A = _ALPHA_LIST_[EntityNo, PixelIndex]
	A = A - _A_
	If A < 0 Then A = 0
	_ALPHA_LIST_[EntityNo, PixelIndex] = A
	SetShaderConstantArrayFloatByName(ShaderIndex, "alpha_mask", PixelIndex, A)
	
	
EndFunction


////////////////////////////////////////////////////////////////////////////////////////////////////////////

Function _INIT_(_S_ As String, _Q_ As String, _L_ As Float, _T_ As Float)
	Local i As Integer, j As Integer
	_VIEW_LEFT_ = _L_
	_VIEW_TOP_ = _T_
	
	
	//i = LoadImage(back_image.png")
	Local Mem As Integer, W As Integer, H As Integer
	Local Offset As Integer, C As Integer, R As Integer, G As Integer, B As Integer
	W = 256 + (Random() >> 8)
	H = 256 + (Random() >> 8)
	Mem = CreateMemblock(12 + W * H * 4)
	SetMemblockInt(Mem, 0, W)
	SetMemblockInt(Mem, 4, H)
	SetMemblockInt(Mem, 8, 32)
	Offset = 12
	For i = 0 To H - 1
		For j = 0 To W - 1
			R = (i ~~ j) && 255
			G = R
			B = R
			C = 0xFF000000
			C = C || (B << 16) || (G << 8) || R
			SetMemblockInt(Mem, Offset, C)
			Offset = Offset + 4
		Next
	Next
	i = CreateImageFromMemblock(Mem)
	_BACK_SPRITE_ = CreateSprite(i)
	DeleteMemblock(Mem)
	SetSpritePosition(_BACK_SPRITE_, _VIEW_LEFT_, _VIEW_TOP_)
	
	
	
	//i = LoadImage(_S_)
	i = _CREATE_RANDOM_IMAGE_(W, H)
	
	
	j = _FIX_IMAGE_SIZE_(i)
	_SUB_DIVIDE_IMAGE_(j)
	_CREATE_CURSOR_IMAGE_(_Q_)
EndFunction

Function _CREATE_CURSOR_IMAGE_(_Q_ As String)
	/*
	Local i As Integer, Mem As Integer
	i = LoadImage(_Q_)
	Mem = CreateMemblockFromImgae(i)
	_FILL_CURSOR_ARRAY_(Mem)
	DeleteMemblock(Mem)
	DeleteImage(i)
	//EndFunction
	*/
	
	//Again no media;
	
	Local i As Integer, j As Integer, k As Integer
	Local AlphaStart As Float, AlphaEnd As Float, dAlpha As Float
	Local Mem As Integer
	_CURSOR_WIDTH_ = 40
	_CURSOR_HEIGHT_ = _CURSOR_WIDTH_
	_CURSOR_MID_X_ = _CURSOR_WIDTH_/2
	_CURSOR_MID_Y_ = _CURSOR_HEIGHT_/2
	
	Mem = CreateMemblock(12 + _CURSOR_WIDTH_ * _CURSOR_HEIGHT_ * 4)
	SetMemblockInt(Mem, 0, _CURSOR_WIDTH_)
	SetMemblockInt(Mem, 4, _CURSOR_HEIGHT_)
	SetMemblockInt(Mem, 8, 32)
	
	AlphaStart = 255
	AlphaEnd = 10
	dAlpha = _CURSOR_WIDTH_/2
	dAlpha = (AlphaStart - AlphaEnd)/dAlpha
	
	k = 0
	For i = _CURSOR_WIDTH_/2 To 1 Step -1
		j = _FILL_CIRCLE_IMAGE_(Mem, _CURSOR_MID_X_, _CURSOR_MID_Y_, i, AlphaEnd)
		AlphaEnd = AlphaEnd + dAlpha
		If GetImageExists(k) Then DeleteImage(k)
		k = j
		DeleteMemblock(Mem)
		Mem = CreateMemblockFromImage(k)
	Next
	
	_FILL_CURSOR_ARRAY_(Mem)
	DeleteMemblock(Mem)
	If GetImageExists(k) Then DeleteImage(k)
	
	
EndFunction

Function _FILL_CURSOR_ARRAY_(_O_ As Integer)
	Local W As Integer, H As Integer
	Local X As Integer, Y As Integer
	Local i As Integer, f As Float
	Local Offset As Integer
	W = GetMemblockInt(_O_, 0)
	H = GetMemblockInt(_O_, 4)
	
	Offset = 12
	For Y = 0 To H - 1
		For X = 0 To W - 1
			i = GetMemblockInt(_O_, Offset) && 255
			f = i
			_CURSOR_IMAGE_[X, Y] = f
			Offset = Offset + 4
		Next
	Next
EndFunction


Function _FILL_CIRCLE_IMAGE_(_O_ As Integer, _X_ As Integer, _Y_ As Integer, _R_ As Integer, _C_ As Integer) //Yes we can!

     
    Local Ax As Integer, Ay As Integer, E As Integer, L As Integer
    Local XStart As Integer, XStop As Integer, LY As Integer, Offset As Integer
    Local XLim As Integer, YLim As Integer, Pitch As Integer
     
     
    Pitch = GetMemblockInt(_O_, 0)
    XLim = Pitch - 1
    YLim = GetMemblockInt(_O_, 4) - 1
     
     
    Ax = _R_
    Ay = 0
    E = 0
     
     
    Do
        If Ax < Ay Then Exit
        XStart = _X_ - Ax
        XStop = _X_ + Ax
        If XStart < 0 Then XStart = 0
        If XStop > XLim Then XStop = XLim
         
         
        //drawline(xtart, Ly, xstop, Ly, _C_)
        Ly = _Y_ + Ay
        If (Ly >= 0) And (Ly <= YLim)
            Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
            For L = XStart To XStop
                SetMemblockInt(_O_, Offset, _C_)
                Offset = Offset + 4
            Next
        EndIf
 
        Ly = _Y_ - Ay
        If (Ly >= 0) And (Ly <= YLim)
            Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
            For L = XStart To XStop
                SetMemblockInt(_O_, Offset, _C_)
                Offset = Offset + 4
            Next
        EndIf
 
         
        XStart = _X_ - Ay
        XStop = _X_ + Ay
        If XStart < 0 Then XStart = 0
        If XStop > XLim Then XStop = XLim
        Ly = _Y_ + Ax
        If (Ly >= 0) And (Ly <= YLim)
            Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
            For L = XStart To XStop
                SetMemblockInt(_O_, Offset, _C_)
                Offset = Offset + 4
            Next
        EndIf
 
         
        Ly = _Y_ - Ax
        If (Ly >= 0) And (Ly <= YLim)
            Offset = (3 + (XStart + LY * Pitch)) << 2  //12 + (X + Y * W) * 4
            For L = XStart To XStop
                SetMemblockInt(_O_, Offset, _C_)
                Offset = Offset + 4
            Next
        EndIf
         
         
        If E <= 0
            Ay = Ay + 1
            E = E + Ay * 2 + 1
        EndIf
         
        If E > 0
            Ax = Ax - 1
            E = E - Ax * 2 + 1
        EndIf
    Loop   
     
    X = CreateImageFromMemblock(_O_)
     
EndFunction X


Function _CREATE_RANDOM_IMAGE_(_W_ As Integer, _H_ As Integer)
	Local Mem As Integer, Image As Integer, X As Integer, Y As Integer
	Local Offset As Integer, Pixel As Integer
	Mem = CreateMemblock(12 + _W_ * _H_ * 4)
	SetMemblockInt(Mem, 0, _W_)
	SetMemblockInt(Mem, 4, _H_)
	SetMemblockInt(Mem, 8, 32)
	Offset = 12
	For Y = 0 To _H_ - 1
		For X = 0 To _W_ - 1
			Pixel = MakeColor(Random() >> 8, Random() >> 8, Random() >> 8)
			Pixel = Pixel || 0xFF000000
			SetMemblockInt(Mem, Offset, Pixel)
			Offset = Offset + 4
		Next
	Next
	Image = CreateImageFromMemblock(Mem)
	DeleteMemblock(Mem)
EndFunction Image


Function _SUB_DIVIDE_IMAGE_(_I_ As Integer)
	Local ImageMem As Integer, ImageWidth As Integer, ImageHeight As Integer
	Local X As Integer, Y As Integer, P As Integer, Q As Integer, XTiles As Integer, YTiles As Integer
	Local i As Integer, j As Integer, sx As Integer, sy As Integer
	Local ImageLinePicth As Integer, ImageBlockPitch As Integer
	Local NewMem As Integer, BlitSize As Integer
	Local OffsetA As Integer, OffsetB As Integer, OffsetC As Integer
	
	For i = 0 To _MAX_SPRITES_ - 1
		If GetSpriteExists(_SPRITE_LIST_[i])
			DeleteImage(_IMAGE_LIST_[i])
			DeleteSprite(_SPRITE_LIST_[i])
			DeleteShader(_SHADER_LIST_[i])
		EndIf
	Next
	
	ImageMem = CreateMemblockFromImage(_I_)
	ImageWidth = GetMemblockInt(ImageMem, 0)
	ImageHeight = GetMemblockInt(ImageMem, 4)
	ImageLinePicth = ImageWidth << 2
	ImageBlockPitch = ImageLinePicth << _SPRITE_SIZE_BITS_
	XTiles = ImageWidth >> _SPRITE_SIZE_BITS_
	YTiles = ImageHeight >> _SPRITE_SIZE_BITS_
	_SPRITE_COUNT_X_ = XTiles
	_SPRITE_COUNT_Y_ = YTiles
	
	
	NewMem = CreateMemblock(12 + (1 << (_SPRITE_SIZE_BITS_ * 2 + 2)))
	BlitSize = 1 << (_SPRITE_SIZE_BITS_ + 2)
	
	j = 1 << _SPRITE_SIZE_BITS_
	SetMemblockInt(NewMem, 0, j)
	SetMemblockInt(NewMem, 4, j)
	SetMemblockInt(NewMem, 8, 32)
	
	OffsetA = 12
	For Y = 0 To YTiles - 1
		For X = 0 To XTiles - 1
			OffsetC = 12
			OffsetB = OffsetA + X * BlitSize
			For i = 0 To j - 1
				CopyMemblock(ImageMem, NewMem, OffsetB, OffsetC, BlitSize)
				OffsetB = OffsetB + ImageLinePicth
				OffsetC = OffsetC + BlitSize
			Next
			_IMAGE_LIST_[TotalEntities] = CreateImageFromMemblock(NewMem)
			SetImageTransparentColor(_IMAGE_LIST_[TotalEntities], 0, 0, 0)
			_SPRITE_LIST_[TotalEntities] = CreateSprite(_IMAGE_LIST_[TotalEntities])
			SetSpriteTransparency(_SPRITE_LIST_[TotalEntities], 1)
			SetSpritePosition(_SPRITE_LIST_[TotalEntities], _VIEW_LEFT_ + (X << _SPRITE_SIZE_BITS_), _VIEW_TOP_ + (Y << _SPRITE_SIZE_BITS_))
			//SetSpriteColor(_SPRITE_LIST_[TotalEntities], Random()>>8, Random()>>8, Random()>>8, 255)
			
			_SHADER_LIST_[TotalEntities] = LoadShader("scratch.vs", "scratch.ps")
			
			For sy = 0 To 1023
				SetShaderConstantArrayByName(_SHADER_LIST_[TotalEntities], "alpha_mask", sy, 255, 0, 0, 0)
				_ALPHA_LIST_[TotalEntities, sy] = 255
			Next
			SetSpriteShader(_SPRITE_LIST_[TotalEntities], _SHADER_LIST_[TotalEntities])
			TotalEntities = TotalEntities + 1
		Next
		OffsetA = OffsetA + ImageBlockPitch
	Next
	
	DeleteMemblock(ImageMem)
	DeleteMemblock(NewMem)
	
	_TOTAL_SPRITES_ = TotalEntities
	
	
EndFunction



Function _FIX_SIZE_(_X_ As Integer)
	//round up the integer value
	//to the multiplies of 64's
	//12 -> 32
	//257 -> 288
	//256 -> 256
	Local A As Integer, B As Integer, S As Integer
	S = 1 << _SPRITE_SIZE_BITS_
	B = S - 1
	A = _X_ && B
	If A = 0 Then ExitFunction _X_
	A = _X_ >> _SPRITE_SIZE_BITS_
	A = (A + 1) << _SPRITE_SIZE_BITS_
EndFunction A
	





Function _FIX_IMAGE_SIZE_(_I_ As Integer)
	//Create a new Image with the sizes of 64's
	//e.g. 123x61 -> 128x64
	//65x11 -> 128x64
	//and return the new image index
	
	Local Mem As Integer, NewMem As Integer, BlitMem As Integer
	Local ImageWidth As Integer, ImageHeight As Integer, NewWidth As Integer, NewHeight As Integer
	Local X As Integer, Y As Integer, ImagePitch As Integer, NewPicth As Integer
	Local ImageOffset As Integer, NewOffset As Integer
	Local Pixel As Integer, FixedImage As Integer
	
	Mem = CreateMemblockFromImage(_I_)
	ImageWidth = GetMemblockInt(Mem, 0)
	ImageHeight = GetMemblockInt(Mem, 4)
	NewWidth = _FIX_SIZE_(ImageWidth)
	NewHeight = _FIX_SIZE_(ImageHeight)
	
	NewMem = CreateMemblock(12 + NewWidth * NewHeight * 4)
	SetMemblockInt(NewMem, 0, NewWidth)
	SetMemblockInt(NewMem, 4, NewHeight)
	
	ImagePitch = ImageWidth * 4
	NewPitch = NewWidth * 4
	BlitMem = CreateMemblock(ImagePitch)
	
	
	ImageOffset = 12
	NewOffset = 12
	//simply copy pixels, line by line
	For Y = 0 To ImageHeight - 1
		CopyMemblock(Mem, NewMem, ImageOffset, NewOffset, ImagePitch)
		ImageOffset = ImageOffset + ImagePitch
		NewOffset = NewOffset + NewPitch
	Next
	
	
	FixedImage = CreateImageFromMemblock(NewMem)
	DeleteMemblock(Mem)
	DeleteMemblock(NewMem)
	DeleteMemblock(BlitMem)
EndFunction FixedImage


Posted: 10th Apr 2022 11:06
The code with media is much more simple.

+ Code Snippet
// Project: Shader Scratch 
// Created: 2022-04-08

// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "Shader Scratch" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 0, 1 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts


#Constant _SPRITE_SIZE_BITS_ 5
#Constant _MAX_SPRITES_ 2048
Global _IMAGE_LIST_ As Integer[_MAX_SPRITES_]
Global _SPRITE_LIST_ As Integer[_MAX_SPRITES_]
Global _SHADER_LIST_ As Integer[_MAX_SPRITES_]
Global _ALPHA_LIST_ As Float[_MAX_SPRITES_, 1024]  //If you change the spritesizebits, you must change here also!
Global _SPRITE_COUNT_X_ As Integer
Global _SPRITE_COUNT_Y_ As Integer
Global _VIEW_LEFT_ As Float, _VIEW_TOP_ As Float
Global _TOTAL_SPRITES_ As Integer
Global _CURSOR_IMAGE_ As Float[1024, 1024]
Global _CURSOR_WIDTH_ As Integer, _CURSOR_HEIGHT_ As Integer
Global _CURSOR_MID_X_ As Float, _CURSOR_MID_Y_ As Float
Global _BACK_SPRITE_ As Integer



_INIT_("base.png", "scratch.png", "cursor.png", 200, 200)  //image to be revealed, image to be scratched, the image of the cursor, left and top positions


Do
	If GetPointerState() Then _SCRATCH_(GetPointerX(), GetPointerY())
    Print( ScreenFPS() )
    Sync()
Loop


Function _SCRATCH_(_X_ As Float, _Y_ As Float)
	Local X As Integer, Y As Integer
	Local XStart As Float, dY As Float, dX As Float
	
	XStart = _X_ - _CURSOR_MID_X_
	dY = _Y_ - _CURSOR_MID_Y_
	
	For Y = 0 To _CURSOR_HEIGHT_ - 1
		dX = XStart
		For X = 0 To _CURSOR_WIDTH_ - 1
			_SET_PIXELS_ALPHA_(dX, dY, _CURSOR_IMAGE_[X, Y])
			dX = dX + 1
		Next
		dY = dY + 1
	Next
	
EndFunction

Function _SET_PIXELS_ALPHA_(_X_ As Float, _Y_ As Float, _A_ As Float)
	Local X As Integer, Y As Integer, Sy As Integer
	Local EntityNo As Integer, ShaderIndex As Integer, PixelIndex As Integer
	Local A As Float
	X = Floor(_X_ - _VIEW_LEFT_)
	If X < 0 Then ExitFunction
	Y = Floor(_Y_ - _VIEW_TOP_)
	If Y < 0 Then ExitFunction
	
	
	Sx = X >> _SPRITE_SIZE_BITS_
	If Sx >= _SPRITE_COUNT_X_ Then ExitFunction
	Sy = Y >> _SPRITE_SIZE_BITS_
	If Sy >= _SPRITE_COUNT_Y_ Then ExitFunction
	EntityNo = Sx + Sy * _SPRITE_COUNT_X_
	ShaderIndex = _SHADER_LIST_[EntityNo]
	X = X - (Sx << _SPRITE_SIZE_BITS_)
	Y = Y - (Sy << _SPRITE_SIZE_BITS_)
	PixelIndex = X + (Y << _SPRITE_SIZE_BITS_)
	A = _ALPHA_LIST_[EntityNo, PixelIndex]
	A = A - _A_
	If A < 0 Then A = 0
	_ALPHA_LIST_[EntityNo, PixelIndex] = A
	SetShaderConstantArrayFloatByName(ShaderIndex, "alpha_mask", PixelIndex, A)
	
	
EndFunction


////////////////////////////////////////////////////////////////////////////////////////////////////////////

Function _INIT_(_R_ As String, _S_ As String, _Q_ As String, _L_ As Float, _T_ As Float)
	Local i As Integer, j As Integer, Mem As Integer
	
	_VIEW_LEFT_ = _L_
	_VIEW_TOP_ = _T_
	
	
	i = LoadImage(_R_)
	_BACK_SPRITE_ = CreateSprite(i)
	SetSpritePosition(_BACK_SPRITE_, _VIEW_LEFT_, _VIEW_TOP_)
	
	
	
	i = LoadImage(_S_)
	j = _FIX_IMAGE_SIZE_(i)
	_SUB_DIVIDE_IMAGE_(j)
	i = LoadImage(_Q_)
	Mem = CreateMemblockFromImage(i)
	_FILL_CURSOR_ARRAY_(Mem)
	DeleteMemblock(Mem)
	DeleteImage(i)
	
EndFunction


Function _FILL_CURSOR_ARRAY_(_O_ As Integer)
	Local W As Integer, H As Integer
	Local X As Integer, Y As Integer
	Local i As Integer, f As Float
	Local Offset As Integer
	_CURSOR_WIDTH_ = GetMemblockInt(_O_, 0)
	_CURSOR_HEIGHT_ = GetMemblockInt(_O_, 4)

	_CURSOR_MID_X_ = _CURSOR_WIDTH_/2
	_CURSOR_MID_Y_ = _CURSOR_HEIGHT_/2

	
	Offset = 12
	For Y = 0 To _CURSOR_HEIGHT_ - 1
		For X = 0 To _CURSOR_WIDTH_ - 1
			i = GetMemblockInt(_O_, Offset) && 255
			f = i
			_CURSOR_IMAGE_[X, Y] = f
			Offset = Offset + 4
		Next
	Next
	
EndFunction



Function _SUB_DIVIDE_IMAGE_(_I_ As Integer)
	Local ImageMem As Integer, ImageWidth As Integer, ImageHeight As Integer
	Local X As Integer, Y As Integer, P As Integer, Q As Integer, XTiles As Integer, YTiles As Integer
	Local i As Integer, j As Integer, sx As Integer, sy As Integer
	Local ImageLinePicth As Integer, ImageBlockPitch As Integer
	Local NewMem As Integer, BlitSize As Integer
	Local OffsetA As Integer, OffsetB As Integer, OffsetC As Integer
	
	For i = 0 To _MAX_SPRITES_ - 1
		If GetSpriteExists(_SPRITE_LIST_[i])
			DeleteImage(_IMAGE_LIST_[i])
			DeleteSprite(_SPRITE_LIST_[i])
			DeleteShader(_SHADER_LIST_[i])
		EndIf
	Next
	
	ImageMem = CreateMemblockFromImage(_I_)
	ImageWidth = GetMemblockInt(ImageMem, 0)
	ImageHeight = GetMemblockInt(ImageMem, 4)
	ImageLinePicth = ImageWidth << 2
	ImageBlockPitch = ImageLinePicth << _SPRITE_SIZE_BITS_
	XTiles = ImageWidth >> _SPRITE_SIZE_BITS_
	YTiles = ImageHeight >> _SPRITE_SIZE_BITS_
	_SPRITE_COUNT_X_ = XTiles
	_SPRITE_COUNT_Y_ = YTiles
	
	
	NewMem = CreateMemblock(12 + (1 << (_SPRITE_SIZE_BITS_ * 2 + 2)))
	BlitSize = 1 << (_SPRITE_SIZE_BITS_ + 2)
	
	j = 1 << _SPRITE_SIZE_BITS_
	SetMemblockInt(NewMem, 0, j)
	SetMemblockInt(NewMem, 4, j)
	SetMemblockInt(NewMem, 8, 32)
	
	OffsetA = 12
	For Y = 0 To YTiles - 1
		For X = 0 To XTiles - 1
			OffsetC = 12
			OffsetB = OffsetA + X * BlitSize
			For i = 0 To j - 1
				CopyMemblock(ImageMem, NewMem, OffsetB, OffsetC, BlitSize)
				OffsetB = OffsetB + ImageLinePicth
				OffsetC = OffsetC + BlitSize
			Next
			_IMAGE_LIST_[TotalEntities] = CreateImageFromMemblock(NewMem)
			SetImageTransparentColor(_IMAGE_LIST_[TotalEntities], 0, 0, 0)
			_SPRITE_LIST_[TotalEntities] = CreateSprite(_IMAGE_LIST_[TotalEntities])
			SetSpriteTransparency(_SPRITE_LIST_[TotalEntities], 1)
			SetSpritePosition(_SPRITE_LIST_[TotalEntities], _VIEW_LEFT_ + (X << _SPRITE_SIZE_BITS_), _VIEW_TOP_ + (Y << _SPRITE_SIZE_BITS_))
			//SetSpriteColor(_SPRITE_LIST_[TotalEntities], Random()>>8, Random()>>8, Random()>>8, 255)
			
			_SHADER_LIST_[TotalEntities] = LoadShader("scratch.vs", "scratch.ps")
			
			For sy = 0 To 1023
				SetShaderConstantArrayByName(_SHADER_LIST_[TotalEntities], "alpha_mask", sy, 255, 0, 0, 0)
				_ALPHA_LIST_[TotalEntities, sy] = 255
			Next
			SetSpriteShader(_SPRITE_LIST_[TotalEntities], _SHADER_LIST_[TotalEntities])
			TotalEntities = TotalEntities + 1
		Next
		OffsetA = OffsetA + ImageBlockPitch
	Next
	
	DeleteMemblock(ImageMem)
	DeleteMemblock(NewMem)
	
	_TOTAL_SPRITES_ = TotalEntities
	
	
EndFunction



Function _FIX_SIZE_(_X_ As Integer)
	//round up the integer value
	//to the multiplies of 64's
	//12 -> 64
	//257 -> 320
	//256 -> 256
	Local A As Integer, B As Integer, S As Integer
	S = 1 << _SPRITE_SIZE_BITS_
	B = S - 1
	A = _X_ && B
	If A = 0 Then ExitFunction _X_
	A = _X_ >> _SPRITE_SIZE_BITS_
	A = (A + 1) << _SPRITE_SIZE_BITS_
EndFunction A
	





Function _FIX_IMAGE_SIZE_(_I_ As Integer)
	//Create a new Image with the sizes of 64's
	//e.g. 123x61 -> 128x64
	//65x11 -> 128x64
	//and return the new image index
	
	Local Mem As Integer, NewMem As Integer, BlitMem As Integer
	Local ImageWidth As Integer, ImageHeight As Integer, NewWidth As Integer, NewHeight As Integer
	Local X As Integer, Y As Integer, ImagePitch As Integer, NewPicth As Integer
	Local ImageOffset As Integer, NewOffset As Integer
	Local Pixel As Integer, FixedImage As Integer
	
	Mem = CreateMemblockFromImage(_I_)
	ImageWidth = GetMemblockInt(Mem, 0)
	ImageHeight = GetMemblockInt(Mem, 4)
	NewWidth = _FIX_SIZE_(ImageWidth)
	NewHeight = _FIX_SIZE_(ImageHeight)
	
	NewMem = CreateMemblock(12 + NewWidth * NewHeight * 4)
	SetMemblockInt(NewMem, 0, NewWidth)
	SetMemblockInt(NewMem, 4, NewHeight)
	
	ImagePitch = ImageWidth * 4
	NewPitch = NewWidth * 4
	BlitMem = CreateMemblock(ImagePitch)
	
	
	ImageOffset = 12
	NewOffset = 12
	//simply copy pixels, line by line
	For Y = 0 To ImageHeight - 1
		CopyMemblock(Mem, NewMem, ImageOffset, NewOffset, ImagePitch)
		ImageOffset = ImageOffset + ImagePitch
		NewOffset = NewOffset + NewPitch
	Next
	
	
	FixedImage = CreateImageFromMemblock(NewMem)
	DeleteMemblock(Mem)
	DeleteMemblock(NewMem)
	DeleteMemblock(BlitMem)
EndFunction FixedImage




Posted: 10th Apr 2022 21:55
I ajusted the pixel shader a little bit;

scratch.ps;
+ Code Snippet
varying vec2 uvVarying;
varying vec2 posVarying;

uniform sampler2D texture0;
uniform vec2 agk_resolution;
uniform float alpha_mask [1024];

void main()
{
	int x, y, p; 
	x = int(uvVarying.x * 32.0);
	y = int(uvVarying.y * 32.0);
	p = (x + y * 32);
	float alpha = alpha_mask[p];

	vec4 texturecolor = texture2D(texture0, uvVarying);
	gl_FragColor.r = texturecolor.r * alpha/255.0;
	gl_FragColor.g = texturecolor.g * alpha/255.0;
	gl_FragColor.b = texturecolor.b * alpha/255.0;
	gl_FragColor.a = alpha;
}





Memblock version is faster, on my PC.
Posted: 11th Apr 2022 0:16
Arch-OK,

Thank you, the codes are wonderful. All works well. I do notice that sometimes when I use the 1024 array index in the shader ps code, it will not run because the error message states that the vs and ps shader codes failed to link because it is calculating that there will be more than 1024 indexes. It seems that 1024 is the limit. I do not know if anyone else has experienced this.

Do you know how the arrayIndex part of the SetShaderConstantArrayByName (arrayIndex - The index of the element within the array to modify) works. It seems that the command sends the specific index number to the shader, but is there a way to use that submitted index number and not have to calculate the index in the shader ps code?

I am carefully studying your code and will see if I can segment just one sprite into 1024 segments which the shader will then change just that segment of the image for the one sprite. I do not see much difference in the performance (FPS) between either the provided image or memory block codes. Both show about 300 FPS when scratching and 900 FPS when not pressing the mouse button.

Thank you very much for your help. This has been most helpful and a great learning experience.
Posted: 11th Apr 2022 6:56
You are welcome

The last index is 1023 not 1024 (0 - 1023) but I can not say that the limit is this or that as I mentioned before I am not into the shaders and even agk itself that much.

The command "SetShaderConstantArrayByName" sends 1 or 2 or 3 or max 4 float values to the shader. In the scratch code it sends 1 float because our array is 1024 floats, not vec2, vec3 or vec4. Also the size of the float variable could be 11 bits, 16 bits, 32 and 64 bits, I always assumed it is 32 bits.

I tried it with "UINT" variable at the shader but 0xFFFFFFFF was a Nan/QNan or something like that so the alpha channel did not work as using float array. I was Imagining to fit 4 pixel's alpha value in to 1 integer (4 x 8 = 32 bits) so less usage of sprites and more work for shader(extracting corresponding byte from that integer). It just failed to set the alpha value because agk sends only floats???

Instead of "SetShaderConstantArrayByName" there is "SetShaderConstantByName" command.

I'd go with memblocks anyway because I must have the control to the last bit of my code, even I'll create a dll for faster memblock usage but not now, I'm on an another project.

Final notes, yes my codes are unique but usually what happens is this, I do hard code a program, it works and gives the expected results and then I learn/realise a command of AppGameKit that I did not know before, and laugh at my code because there was a simple and a fast way with the command I've newly learned.

All I mean is, this scratch code is and never be the best solution, so I hope you find a faster and reliable way and share with us.
Posted: 11th Apr 2022 21:00
It's not shader code but I did this some time ago for a small project, hope it's of some use.

+ Code Snippet
#constant KEY_ESCAPE 27
#constant SCREEN_WIDTH 1024
#constant SCREEN_HEIGHT 768
#constant SCREEN_DEPTH 32

SetErrorMode(2)

SetWindowTitle( "ScratchImage" )
SetWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT, 0)
SetWindowAllowResize(0)
SetOrientationAllowed(1, 0, 0, 0)

SetVirtualResolution(SCREEN_WIDTH, SCREEN_HEIGHT)
SetSyncRate(60, 0)
SetScissor(0,0,0,0)
UseNewDefaultFonts(1)

type ImagePosition
  x as float
  y as float
endtype

ImageXY as ImagePosition[0]

// overlay image
imgRender = LoadImage("overlayzebra.png")

// image is hidden by overlay
imgZebra = LoadImage("zebra.png")
sprZebra = CreateSprite(imgZebra)

pixelData = SCREEN_WIDTH*SCREEN_HEIGHT*4

do
  ClearScreen()
  
  x2# = GetPointerX()
  y2# = GetPointerY()
 
  SetRenderToImage(imgRender, 0)
  
  if (GetPointerState() = 1)
    for n = 0 to ImageXY.length
	  if (ImageXY[n].x > 0) OR (ImageXY[n].y > 0)
        DrawEllipse(ImageXY[n].x, ImageXY[n].y, 24, 24, MakeColor(255, 100, 100), MakeColor(255, 100, 100), 2)
      endif
    next
  endif
  
  SetRenderToScreen()
  
  if (GetSpriteExists(sprRender) = 1)
    sprRender2 = CloneSprite(sprRender)
    DeleteSprite(sprRender)
  endif 	  
 
  SetImageTransparentColor(imgRender, 255, 100, 100)
  sprRender = CreateSprite(imgRender)
  SetSpriteTransparency(sprRender, 1)
  DeleteSprite(sprRender2)
  
  ImageXY.length = 0
  
  if (GetPointerPressed() = 1)  
    x1# = GetPointerX()
    y1# = GetPointerY()
  endif
  
  if (GetPointerState() = 1)
    diffX# = (x2# - x1#)
    diffY# = (y2# - y1#)
	
    getSteps = GetDistance(diffX#, diffY#)
    
    dx# = (diffX# / getSteps)
    dy# = (diffY# / getSteps)
    
    x# = x1#
    y# = y1#
    
    if (getSteps = 0)
	  imgLength = ImageXY.length
	  
         ImageXY[imgLength].x = x1#
         ImageXY[imgLength].y = y1#
	else	
      for i = 0 to getSteps
        x# = x# + dx#
        y# = y# + dy#
    
        imgLength = ImageXY.length
           
        ImageXY[imgLength].x = x#
        ImageXY[imgLength].y = y#
    
        x1# = x#
        y1# = y#
        
        imgLength = ImageXY.length + 1
    
        ImageXY.length = imgLength
     next
    endif
  endif
  
  if (GetRawKeyState(KEY_ESCAPE) = 1)
    end
  endif
  
  sync()
loop

function GetDistance(diff_X#, diff_Y#)
  getD# = Sqrt((diff_X# * diff_X#) + (diff_Y# * diff_Y#))
endfunction getD#

Posted: 12th Apr 2022 6:45
very nice @tboy

As I mentinoed before, "then I learn/realise a command of AppGameKit that I did not know before, and laugh at my code because there was a simple and a fast way with the command I've newly learned."
-SetRenderToImage
-SetRenderToScreen
-CloneSprite
I learned these commands now, thanks for your example.
Posted: 12th Apr 2022 15:39
Thank you, that's great option @ tboy. Works slightly quicker (more FPS) than the subdivided image with sprites and shaders. I am just struggling with the shader code trying to get just one image and one sprite to change alpha--always just turns black, but not transparent.

This might possibly work but I would have to change the HUD location of the scratch off map as this seems to be limited to a x, y position of 0, 0 because of where the image rendered to is placed. Also have to add a color layer to the scratch off image that will match the SetImageTransparentColor. I do not know how to do that just using AppGameKit code, so it requires loading an image.

I was able to reduce the code that you provided a bit as follows:

+ Code Snippet
// Project: ScratchImagenoshader 
// Created: 2022-04-11

#constant KEY_ESCAPE 27
#constant SCREEN_WIDTH 1024
#constant SCREEN_HEIGHT 768
#constant SCREEN_DEPTH 32
 
SetErrorMode(2)
 
SetWindowTitle( "ScratchImagenoshader" )
SetWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT, 0)
SetWindowAllowResize(0)
SetOrientationAllowed(1, 0, 0, 0)
 
SetVirtualResolution(SCREEN_WIDTH, SCREEN_HEIGHT)
SetSyncRate(0, 0)
SetScissor(0,0,0,0)
UseNewDefaultFonts(1)

SetRawMouseVisible(0) //hide mouse cursor
 
Global TSprite as Integer
Global CircleImageID as Integer
Global x2# as Float
Global y2# as Float
Global rechide as Integer //sprite number for hidden rectangle

Global rechImageID as Integer //image number for hidden sprite

DrawEllipse(12, 12, 12, 12, MakeColor(200, 200, 200), MakeColor(200, 200, 200), 1)
CircleImageID=GetImage(0,0,48,48)


TSpriteID=CreateSprite(CircleImageID) //pointer sprite
SetSpritePosition(TSpriteID, 700, 700)
SetSpriteDepth(TSpriteID, 4)
SetSpriteOffSet(TSpriteID, 12, 12)
ClearScreen()

DrawBox(0, 0, 512, 384, MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), MakeColor(10,10,255), 1)  //blue image for covered sprite
rechImageID=GetImage(0, 0, 512, 384)
ClearScreen()

rechide = CreateSprite(rechImageID)  //create covered blue sprite
SetSpritePosition(rechide, 0, 0)
SetSpriteDepth(rechide, 9)

 
// overlay image
imgRender = LoadImage("gray.png")


do
  ClearScreen()
 
  SetRenderToImage(imgRender, 0)
   
  if (GetPointerState() = 1)
	  x2# = GetPointerX()
	  y2# = GetPointerY()
	  
      SetSpritePositionByOffset(TSpriteID, x2#, y2#)
      DrawEllipse(x2#, y2#, 24, 24, MakeColor(255, 100, 100), MakeColor(255, 100, 100), 1)  //set ellipse to same color as transparency color
  endif
   
  SetRenderToScreen()
  
  x2# = GetPointerX()
  y2# = GetPointerY()
  SetSpritePositionByOffset(TSpriteID, (x2#/SCREEN_WIDTH) * GetSpriteWidth(rechide), (y2#/SCREEN_HEIGHT) * GetSpriteHeight(rechide)) //Keep circle sprite on the image
   
  if (GetSpriteExists(sprRender) = 1)
    sprRender2 = CloneSprite(sprRender)
    SetSpriteColor(sprRender2, 100, 100, 100, 255)  //changes scratch off sprite color to gray
    SetSpriteDepth(sprRender2, 8)
    DeleteSprite(sprRender)
  endif      
  
  SetImageTransparentColor(imgRender, 255, 100, 100)
  sprRender = CreateSprite(imgRender)
  SetSpriteColor(sprRender, 100, 100, 100, 255)  //changes scratch off sprite color to gray
  SetSpriteTransparency(sprRender, 1)
  SetSpriteDepth(sprRender, 8)
  DeleteSprite(sprRender2)
    
  if (GetRawKeyState(KEY_ESCAPE) = 1)
    end
  endif
  
   Print( ScreenFPS() )
 Sync()
loop
 
Posted: 12th Apr 2022 21:49
shaders cant remember, and giving them a huge array doesnt seem to be a good solution either.
So dont try using shaders for that besides the obvious transparency.
I'd suggest you render the brush onto a black and white mask texture, white for transparency for example and just set the alpha value inside the shader to that value of the mask texture.
That way you can erase your drawings by overwriting the mask with black again.
I think thats how i did it here:
Posted: 12th Apr 2022 22:59
Thank you @janbo. Would your suggestion be any quicker than using something similar to the image overlay that uses setrendertoimage and a transparent color to render a sprite (the suggestion provided by tboy? )