Notifications
Clear all

[Closed] Get VFB while rendering

Is there a way to get a bitmap from the VFB while it’s rendering to get the intermediate results?

I’ve tried a backgroundworker thread to copy the active VFB to a new bitmap every second but that seems to fail spectacularly

8 Replies
 lo1

I don’t know how to do it, but if you figure it out, let me know

The backgroundworker idea sounds interesting. How are you trying to copy the bitmap?

 lo1

Here is a very dirty proof of concept, it seems to work.

try(dnf.close())catch()
str="using System;
"
str+="using System.Runtime.InteropServices;
"
str+="class VFBTestWindowOps
"
str+="{
"	
str+="	 public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
"     
str+="	 [DllImport(\"user32.dll\")]
"
str+="	 private static extern bool GetClientRect(IntPtr hWnd, out RECT rect);
" 
str+="	 public int[] GetClientRect(IntPtr hWnd)
"
str+="   {
"
str+="       RECT rect;
"
str+="       if (GetClientRect(hWnd, out rect))
"
str+="       {
"
str+="           return new int[4] {rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };
"
str+="       }
"
str+="       else return null;
"
str+="    }
"
str+="}
"
	
	
fn CreateWinAssembly =
(
	local csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	local compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		
	compilerParams.ReferencedAssemblies.addRange #("System.dll","System.Windows.Forms.dll","System.Drawing.dll")
	compilerParams.GenerateInMemory = on
	local compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(str)
	
	for er =0 to compilerResults.errors.count-1 do print (compilerResults.errors.item[er].tostring())
	global vfbassembly = compilerResults.CompiledAssembly.CreateInstance "VFBTestWindowOps"	
)

fn getCurrentVFBHandle =
(
	local bmm = "bmm"
	local pattern1 = "* (*:*)"
	local pattern2 = "Clone of *"
	local pattern3 = "*.??? *(*:*)"
	local pattern4 = "Specify Cropping*"
	local result
	local gwT = uiAccessor.getWindowText
	local gWD = uiAccessor.GetWindowDllFileName
	for x in (windows.getChildrenHwnd 0 parent:#max) while result == undefined do
	(
		x = x[1]
		if (local wName = gwT x)!=undefined and (local dll = gWD x)!=undefined and getFileNameFile dll==bmm and \
		matchPattern wName pattern:pattern1 and not matchPattern wName pattern:pattern2 \
		and not matchPattern wName pattern:pattern3 and not matchPattern wName pattern:pattern4 do result = x
	)
	result
)
	
fn getBmp =
(
	if (local hwnd = getCurrentVFBHandle()) != undefined do
	(
		newSize = vfbAssembly.GetClientRect (dotnetobject "system.intptr" hwnd)
		newbmp = dotnetobject "system.drawing.bitmap" newSize[3] newSize[4]
		gfx = (dotnetclass "system.drawing.graphics").fromImage newBmp
		gfx.copyFromScreen newSize[1] newSize[2] 0 0 (dotnetobject "system.drawing.size" newSize[3] newSize[4]) \
			(dotnetclass "system.drawing.CopyPixelOperation").sourceCopy	
		try(pnl.backgroundimage.dispose())catch()
		pnl.backgroundimage = newBmp	
		pnl.invalidate()
	)
)

fn dntTick s e =
(
	getBmp()
)

global dnf = dotnetobject "maxCustomControls.maxForm"
dnf.width = renderWidth
dnf.height = renderHeight
global pnl = dotnetobject "panel"
pnl.dock = pnl.dock.fill	
dnf.controls.add pnl
global dnt = dotnetobject "system.timers.timer" 500
dnF.showInTaskbar = off
wrapper = dotNetObject "maxCustomControls.win32HandleWrapper" (dotNetObject "System.IntPtr" (windows.getMaxHwnd()))
	
dotnet.addEventHandler dnt "Elapsed" dntTick

callbacks.removeScripts id:#vfbtest
callbacks.addScript #preRender "dnt.start()" id:#vfbtest
callbacks.addScript #postRender "dnt.stop()" id:#vfbtest	
createWinAssembly()
dnf.show wrapper

The caveats of this method are that it is based on just copying the screen pixels, not getting an actual bitmap value from the VFB. That means that if the user zooms in or out, you will get the zoomed bitmap, not the 1:1 bitmap.

 lo1

a little less dirty version…

try(dnf.close())catch()
str="using System;
"
str+="using System.Runtime.InteropServices;
"
str+="class VFBTestWindowOps
"
str+="{
"	
str+="	 public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
"     
str+="	 [DllImport(\"user32.dll\")]
"
str+="	 private static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
" 
str+="	 public int[] GetWindowRect(IntPtr hWnd)
"
str+="   {
"
str+="       RECT rect;
"
str+="       if (GetWindowRect(hWnd, out rect))
"
str+="       {
"
str+="           return new int[4] {rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };
"
str+="       }
"
str+="       else return null;
"
str+="    }
"
str+="}
"
	
	
fn CreateWinAssembly =
(
	local csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	local compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		
	compilerParams.ReferencedAssemblies.addRange #("System.dll","System.Windows.Forms.dll","System.Drawing.dll")
	compilerParams.GenerateInMemory = on
	local compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(str)
	
	for er =0 to compilerResults.errors.count-1 do print (compilerResults.errors.item[er].tostring())
	global vfbassembly = compilerResults.CompiledAssembly.CreateInstance "VFBTestWindowOps"	
)

fn getCurrentVFBHandle =
(
	local bmm = "bmm"
	local pattern1 = "* (*:*)"
	local pattern2 = "Clone of *"
	local pattern3 = "*.??? *(*:*)"
	local pattern4 = "Specify Cropping*"
	local result
	local gwT = uiAccessor.getWindowText
	local gWD = uiAccessor.GetWindowDllFileName
	for x in (windows.getChildrenHwnd 0 parent:#max) while result == undefined do
	(
		x = x[1]
		if (local wName = gwT x)!=undefined and (local dll = gWD x)!=undefined and getFileNameFile dll==bmm and \
		matchPattern wName pattern:pattern1 and not matchPattern wName pattern:pattern2 \
		and not matchPattern wName pattern:pattern3 and not matchPattern wName pattern:pattern4 do result = x
	)
	result
)

fn dntTick =
(
	if (local hwnd = getCurrentVFBHandle()) != undefined do
	(
		local newSize = vfbAssembly.GetWindowRect (dotnetobject "system.intptr" (windows.getChildHWND hwnd "BitmapWindow")[1])
		local newbmp = dotnetobject "system.drawing.bitmap" newSize[3] (newSize[4]-32)
		local gfx = (dotnetclass "system.drawing.graphics").fromImage newBmp
		gfx.copyFromScreen newSize[1] (newSize[2]+32) 0 0 (dotnetobject "system.drawing.size" newSize[3] (newSize[4]-32)) \
			(dotnetclass "system.drawing.CopyPixelOperation").sourceCopy	
		gfx.dispose()
		try(pnl.backgroundimage.dispose())catch()
		pnl.backgroundimage = newBmp	
		pnl.invalidate()
	)
)

global dnf = dotnetobject "maxCustomControls.maxForm"
dnf.width = renderWidth
dnf.height = renderHeight
global pnl = dotnetobject "panel"
pnl.dock = pnl.dock.fill	
dnf.controls.add pnl
global dnt = dotnetobject "system.timers.timer" 500
dnF.showInTaskbar = off
dnF.text = "VFB Capture Test"
wrapper = dotNetObject "maxCustomControls.win32HandleWrapper" (dotNetObject "System.IntPtr" (windows.getMaxHwnd()))
	
dotnet.addEventHandler dnt "Elapsed" dntTick

callbacks.removeScripts id:#vfbtest
callbacks.addScript #preRender "dnt.start()" id:#vfbtest
callbacks.addScript #postRender "dnt.stop(); dntTick()" id:#vfbtest	
createWinAssembly()
dnf.show wrapper

 lo1

hmm… it turns out getLastRenderedImage() works just fine as well in a timer… :banghead:
No need for all this crap.

or in other words:

global img

fn dntTick = ::img = getLastRenderedImage copy:off

global dnt = dotnetobject "system.timers.timer" 500
dotnet.addEventHandler dnt "Elapsed" dntTick

callbacks.removeScripts id:#vfbtest
callbacks.addScript #preRender "dnt.start()" id:#vfbtest
callbacks.addScript #postRender "dnt.stop()" id:#vfbtest
1 Reply
(@jonadb)
Joined: 2 years ago

Posts: 0

Ahh that’s a clever trick, thx!

In case you’re wondering I’m need this for a distributed render thing I’m building. That whole socket communication thing I was working on is turning into a pretty nifty framework. One of the plugins I want to build for it is to cluster max sessions into an hybrid activeshade-backburner thing. With this you can have multiple nodes work on a single iRay render with live progressive feedback and an interactive camera… more on that in a few week I hope!

 lo1

That sounds awesome, looking forward to hear more.

little tweak:

global img=#()
 
fn dntTick = append ::img (getLastRenderedImage copy:true)
 
global dnt = dotnetobject "system.timers.timer" 500
dotnet.addEventHandler dnt "Elapsed" dntTick
 
callbacks.removeScripts id:#vfbtest
callbacks.addScript #preRender "dnt.start()" id:#vfbtest
callbacks.addScript #postRender "dnt.stop()" id:#vfbtest

This will result in an array holding snapshots taken at every 0.5 seconds

 lo1

it’s strange that copy:true works fine for you. For me, the values are flipped; specifying false creates a copy and specifying true doesn’t… perhaps it’s version related.

On a different matter, watch out for your RAM usage if you’re storing so many renders. I think you run the risk of crashing the machine during a long render.
I would probably write the images to file and release the memory.