mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-11-18 17:17:31 +00:00
Capture directly from OpenGL framebuffer
- Update capture.dll to read via glReadPixels - Move coroutine wake up into OnWorldPostUpdate - Update resolution checks for new capturing method - Remove fullscreen mode check - Increase screen capture delay
This commit is contained in:
parent
4de83e3dcd
commit
d5cd88a30e
@ -1,4 +1,4 @@
|
||||
; Copyright (c) 2019-2022 David Vogel
|
||||
; Copyright (c) 2019-2023 David Vogel
|
||||
;
|
||||
; This software is released under the MIT License.
|
||||
; https://opensource.org/licenses/MIT
|
||||
@ -15,44 +15,37 @@ Structure QueueElement
|
||||
sy.i
|
||||
EndStructure
|
||||
|
||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
||||
Procedure EnumWindowsProc(hWnd.l, *lParam.Long)
|
||||
Protected lpProc.l
|
||||
GetWindowThreadProcessId_(hWnd, @lpProc)
|
||||
If *lParam\l = lpProc ; Check if current window's processID matches
|
||||
*lParam\l = hWnd ; Replace processID in the param With the hwnd As result
|
||||
ProcedureReturn #False ; Return false to stop iterating
|
||||
Structure GLViewportDims
|
||||
x.i
|
||||
y.i
|
||||
width.i
|
||||
height.i
|
||||
EndStructure
|
||||
|
||||
; Returns the size of the main OpenGL rendering output.
|
||||
ProcedureDLL GetGLViewportSize(*dims.GLViewportDims)
|
||||
If Not *dims
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
glGetIntegerv_(#GL_VIEWPORT, *dims)
|
||||
|
||||
ProcedureReturn #True
|
||||
EndProcedure
|
||||
|
||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
||||
; Returns the first window associated with the given process handle
|
||||
Procedure GetProcHwnd()
|
||||
Protected pID.l = GetCurrentProcessId_()
|
||||
Protected tempParam.l = pID
|
||||
EnumWindows_(@EnumWindowsProc(), @tempParam)
|
||||
If tempParam = pID ; Check if anything was found
|
||||
ProcedureReturn #Null
|
||||
EndIf
|
||||
ProcedureReturn tempParam ; This is a valid hWnd at this point
|
||||
EndProcedure
|
||||
|
||||
; Get the client rectangle of the "Main" window of this process in screen coordinates
|
||||
; Returns the size of the main OpenGL rendering output as a windows RECT.
|
||||
ProcedureDLL GetRect(*rect.RECT)
|
||||
Protected hWnd.l = GetProcHwnd()
|
||||
If Not hWnd
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
If Not *rect
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
GetClientRect_(hWnd, *rect)
|
||||
|
||||
; A RECT consists basically of two POINT structures
|
||||
ClientToScreen_(hWnd, @*rect\left)
|
||||
ClientToScreen_(hWnd, @*rect\Right)
|
||||
|
||||
Protected dims.GLViewportDims
|
||||
glGetIntegerv_(#GL_VIEWPORT, dims)
|
||||
|
||||
*rect\left = dims\x
|
||||
*rect\top = dims\y
|
||||
*rect\right = dims\x + dims\width
|
||||
*rect\bottom = dims\y + dims\height
|
||||
|
||||
ProcedureReturn #True
|
||||
EndProcedure
|
||||
@ -84,7 +77,7 @@ Procedure Worker(*Dummy)
|
||||
sy = Queue()\sy
|
||||
DeleteElement(Queue())
|
||||
UnlockMutex(Mutex)
|
||||
|
||||
|
||||
If sx > 0 And sy > 0
|
||||
ResizeImage(img, sx, sy)
|
||||
EndIf
|
||||
@ -97,53 +90,56 @@ Procedure Worker(*Dummy)
|
||||
EndProcedure
|
||||
|
||||
; Takes a screenshot of the client area of this process' active window.
|
||||
; The portion of the client area that is captured is described by capRect, which is in window coordinates and relative to the client area.
|
||||
; The portion of the client area that is captured is described by capRect, which is in viewport coordinates.
|
||||
; x and y defines the top left position of the captured rectangle in scaled world coordinates. The scale depends on the window to world pixel ratio.
|
||||
; sx and sy defines the final dimensions that the screenshot will be resized to. No resize will happen if set to 0.
|
||||
ProcedureDLL Capture(*capRect.RECT, x.l, y.l, sx.l, sy.l)
|
||||
Protected hWnd.l = GetProcHwnd()
|
||||
If Not hWnd
|
||||
Protected viewportRect.RECT
|
||||
If Not GetRect(@viewportRect)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
Protected rect.RECT
|
||||
If Not GetRect(@rect)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
; Limit the desired capture area to the actual client area of the window.
|
||||
; Limit the desired capture area to the actual client area of the viewport.
|
||||
If *capRect\left < 0 : *capRect\left = 0 : EndIf
|
||||
If *capRect\right > rect\right-rect\left : *capRect\right = rect\right-rect\left : EndIf
|
||||
If *capRect\top < 0 : *capRect\top = 0 : EndIf
|
||||
If *capRect\bottom > rect\bottom-rect\top : *capRect\bottom = rect\bottom-rect\top : EndIf
|
||||
|
||||
imageID = CreateImage(#PB_Any, *capRect\right-*capRect\left, *capRect\bottom-*capRect\top)
|
||||
If *capRect\right < *capRect\left : *capRect\right = *capRect\left : EndIf
|
||||
If *capRect\bottom < *capRect\top : *capRect\bottom = *capRect\top : EndIf
|
||||
If *capRect\right > viewportRect\right : *capRect\right = viewportRect\right : EndIf
|
||||
If *capRect\bottom > viewportRect\bottom : *capRect\bottom = viewportRect\bottom : EndIf
|
||||
|
||||
Protected capWidth = *capRect\right - *capRect\left
|
||||
Protected capHeight = *capRect\bottom - *capRect\top
|
||||
|
||||
imageID = CreateImage(#PB_Any, capWidth, capHeight)
|
||||
If Not imageID
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
; Get DC of window.
|
||||
windowDC = GetDC_(hWnd)
|
||||
If Not windowDC
|
||||
FreeImage(imageID)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
;Protected *pixelBuf = AllocateMemory(3 * width * height)
|
||||
|
||||
hDC = StartDrawing(ImageOutput(imageID))
|
||||
If Not hDC
|
||||
ReleaseDC_(hWnd, windowDC)
|
||||
FreeImage(imageID)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
If Not BitBlt_(hDC, 0, 0, *capRect\right-*capRect\left, *capRect\bottom-*capRect\top, windowDC, *capRect\left, *capRect\top, #SRCCOPY) ; After some time BitBlt will fail, no idea why. Also, that's moments before noita crashes.
|
||||
StopDrawing()
|
||||
ReleaseDC_(hWnd, windowDC)
|
||||
FreeImage(imageID)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
StopDrawing()
|
||||
|
||||
*pixelBuffer = DrawingBuffer()
|
||||
glReadPixels_(*capRect\left, *capRect\top, capWidth, capHeight, #GL_BGR_EXT, #GL_UNSIGNED_BYTE, *pixelBuffer)
|
||||
|
||||
ReleaseDC_(hWnd, windowDC)
|
||||
;For y = 0 To *capRect\height - 1
|
||||
; *Line.Pixel = Buffer + Pitch * y
|
||||
;
|
||||
; For x = 0 To *capRect\width - 1
|
||||
;
|
||||
; *Line\Pixel = ColorTable(pos2) ; Write the pixel directly to the memory !
|
||||
; *Line+Offset
|
||||
;
|
||||
; ; You can try with regular plot to see the speed difference
|
||||
; ; Plot(x, y, ColorTable(pos2))
|
||||
; Next
|
||||
; Next
|
||||
|
||||
StopDrawing()
|
||||
|
||||
LockMutex(Mutex)
|
||||
; Check if the queue has too many elements, if so, wait. (Emulate go's channels)
|
||||
@ -173,13 +169,13 @@ EndProcedure
|
||||
;Capture(123, 123)
|
||||
;Delay(1000)
|
||||
|
||||
; IDE Options = PureBasic 6.00 LTS (Windows - x64)
|
||||
; IDE Options = PureBasic 6.04 LTS (Windows - x64)
|
||||
; ExecutableFormat = Shared dll
|
||||
; CursorPosition = 94
|
||||
; FirstLine = 39
|
||||
; Folding = --
|
||||
; CursorPosition = 126
|
||||
; FirstLine = 98
|
||||
; Folding = -
|
||||
; Optimizer
|
||||
; EnableThread
|
||||
; EnableXP
|
||||
; Executable = capture.dll
|
||||
; Compiler = PureBasic 6.00 LTS (Windows - x86)
|
||||
; Compiler = PureBasic 6.04 LTS (Windows - x86)
|
Binary file not shown.
@ -64,8 +64,8 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
|
||||
end
|
||||
|
||||
local rectTopLeft, rectBottomRight = ScreenCapture.GetRect()
|
||||
if Coords.WindowResolution ~= rectBottomRight - rectTopLeft then
|
||||
error(string.format("window size seems to have changed from %s to %s", Coords.WindowResolution, rectBottomRight - rectTopLeft))
|
||||
if Coords:InternalRectSize() ~= rectBottomRight - rectTopLeft then
|
||||
error(string.format("internal rectangle size seems to have changed from %s to %s", Coords:InternalRectSize(), rectBottomRight - rectTopLeft))
|
||||
end
|
||||
|
||||
local topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
|
||||
@ -108,9 +108,9 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
|
||||
-- Suspend UI drawing for 1 frame.
|
||||
UI:SuspendDrawing(1)
|
||||
|
||||
wait(0)
|
||||
--wait(0)
|
||||
|
||||
-- Fetch coordinates again, as they may have changed.
|
||||
-- Recalculate capture position and rectangle if we are not forcing any capture position.
|
||||
if not pos then
|
||||
topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
|
||||
if outputPixelScale > 0 then
|
||||
@ -120,6 +120,9 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
|
||||
end
|
||||
end
|
||||
|
||||
-- Wait for two frames.
|
||||
wait(1)
|
||||
|
||||
-- The top left world position needs to be upscaled by the pixel scale.
|
||||
-- Otherwise it's not possible to stitch the images correctly.
|
||||
if not ScreenCapture.Capture(topLeftCapture, bottomRightCapture, outputTopLeft, (bottomRightWorld - topLeftWorld) * outputPixelScale) then
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Copyright (c) 2019-2022 David Vogel
|
||||
-- Copyright (c) 2019-2023 David Vogel
|
||||
--
|
||||
-- This software is released under the MIT License.
|
||||
-- https://opensource.org/licenses/MIT
|
||||
@ -53,8 +53,8 @@ function Check:Regular(interval)
|
||||
local topLeft, bottomRight = ScreenCap.GetRect() -- Actual window client area.
|
||||
if topLeft and bottomRight then
|
||||
local actual = bottomRight - topLeft
|
||||
if actual ~= Coords.WindowResolution then
|
||||
Message:ShowWrongResolution(Modification.AutoSet, string.format("Old window resolution is %s. Current resolution is %s.", Coords.WindowResolution, actual))
|
||||
if actual ~= Coords:InternalRectSize() then
|
||||
Message:ShowWrongResolution(Modification.AutoSet, string.format("Internal rectangle size is %s. Current resolution is %s.", Coords:InternalRectSize(), actual))
|
||||
end
|
||||
else
|
||||
Message:ShowRuntimeError("GetRect", "Couldn't determine window resolution.")
|
||||
@ -62,12 +62,12 @@ function Check:Regular(interval)
|
||||
|
||||
-- Check if we have the required settings.
|
||||
local config, magic, patches = Modification.RequiredChanges()
|
||||
if config["fullscreen"] then
|
||||
local expected = tonumber(config["fullscreen"])
|
||||
if expected ~= Coords.FullscreenMode then
|
||||
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Fullscreen mode %s. Expected %s.", Coords.FullscreenMode, expected))
|
||||
end
|
||||
end
|
||||
--if config["fullscreen"] then
|
||||
-- local expected = tonumber(config["fullscreen"])
|
||||
-- if expected ~= Coords.FullscreenMode then
|
||||
-- Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Fullscreen mode %s. Expected %s.", Coords.FullscreenMode, expected))
|
||||
-- end
|
||||
--end
|
||||
if config["window_w"] and config["window_h"] then
|
||||
local expected = Vec2(tonumber(config["window_w"]), tonumber(config["window_h"]))
|
||||
if expected ~= Coords.WindowResolution then
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Copyright (c) 2019-2022 David Vogel
|
||||
-- Copyright (c) 2019-2023 David Vogel
|
||||
--
|
||||
-- This software is released under the MIT License.
|
||||
-- https://opensource.org/licenses/MIT
|
||||
@ -24,6 +24,13 @@ ffi.cdef([[
|
||||
LONG bottom;
|
||||
} RECT;
|
||||
|
||||
typedef struct {
|
||||
LONG x;
|
||||
LONG y;
|
||||
LONG width;
|
||||
LONG height;
|
||||
} GLViewportDims;
|
||||
|
||||
bool GetRect(RECT* rect);
|
||||
bool Capture(RECT* rect, int x, int y, int sx, int sy);
|
||||
]])
|
||||
|
9
init.lua
9
init.lua
@ -108,15 +108,20 @@ end
|
||||
---Called *every* time the game is about to start updating the world.
|
||||
function OnWorldPreUpdate()
|
||||
Message:CatchException("OnWorldPreUpdate", function()
|
||||
|
||||
-- Coroutines aren't run every frame in this lua sandbox, do it manually here.
|
||||
wake_up_waiting_threads(1)
|
||||
--wake_up_waiting_threads(1)
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
---Called *every* time the game has finished updating the world.
|
||||
function OnWorldPostUpdate()
|
||||
Message:CatchException("OnWorldPreUpdate", function()
|
||||
-- Coroutines aren't run every frame in this lua sandbox, do it manually here.
|
||||
wake_up_waiting_threads(1)
|
||||
|
||||
end)
|
||||
|
||||
Message:CatchException("OnWorldPostUpdate", function()
|
||||
-- Reload mod every 60 frames.
|
||||
-- This allows live updates to the mod while Noita is running.
|
||||
|
Loading…
Reference in New Issue
Block a user