[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
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?
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.
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
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
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!
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
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.