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.
|
; This software is released under the MIT License.
|
||||||
; https://opensource.org/licenses/MIT
|
; https://opensource.org/licenses/MIT
|
||||||
@ -15,44 +15,37 @@ Structure QueueElement
|
|||||||
sy.i
|
sy.i
|
||||||
EndStructure
|
EndStructure
|
||||||
|
|
||||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
Structure GLViewportDims
|
||||||
Procedure EnumWindowsProc(hWnd.l, *lParam.Long)
|
x.i
|
||||||
Protected lpProc.l
|
y.i
|
||||||
GetWindowThreadProcessId_(hWnd, @lpProc)
|
width.i
|
||||||
If *lParam\l = lpProc ; Check if current window's processID matches
|
height.i
|
||||||
*lParam\l = hWnd ; Replace processID in the param With the hwnd As result
|
EndStructure
|
||||||
ProcedureReturn #False ; Return false to stop iterating
|
|
||||||
|
; Returns the size of the main OpenGL rendering output.
|
||||||
|
ProcedureDLL GetGLViewportSize(*dims.GLViewportDims)
|
||||||
|
If Not *dims
|
||||||
|
ProcedureReturn #False
|
||||||
EndIf
|
EndIf
|
||||||
|
|
||||||
|
glGetIntegerv_(#GL_VIEWPORT, *dims)
|
||||||
|
|
||||||
ProcedureReturn #True
|
ProcedureReturn #True
|
||||||
EndProcedure
|
EndProcedure
|
||||||
|
|
||||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
; Returns the size of the main OpenGL rendering output as a windows RECT.
|
||||||
; 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
|
|
||||||
ProcedureDLL GetRect(*rect.RECT)
|
ProcedureDLL GetRect(*rect.RECT)
|
||||||
Protected hWnd.l = GetProcHwnd()
|
|
||||||
If Not hWnd
|
|
||||||
ProcedureReturn #False
|
|
||||||
EndIf
|
|
||||||
If Not *rect
|
If Not *rect
|
||||||
ProcedureReturn #False
|
ProcedureReturn #False
|
||||||
EndIf
|
EndIf
|
||||||
|
|
||||||
GetClientRect_(hWnd, *rect)
|
Protected dims.GLViewportDims
|
||||||
|
glGetIntegerv_(#GL_VIEWPORT, dims)
|
||||||
|
|
||||||
; A RECT consists basically of two POINT structures
|
*rect\left = dims\x
|
||||||
ClientToScreen_(hWnd, @*rect\left)
|
*rect\top = dims\y
|
||||||
ClientToScreen_(hWnd, @*rect\Right)
|
*rect\right = dims\x + dims\width
|
||||||
|
*rect\bottom = dims\y + dims\height
|
||||||
|
|
||||||
ProcedureReturn #True
|
ProcedureReturn #True
|
||||||
EndProcedure
|
EndProcedure
|
||||||
@ -97,53 +90,56 @@ Procedure Worker(*Dummy)
|
|||||||
EndProcedure
|
EndProcedure
|
||||||
|
|
||||||
; Takes a screenshot of the client area of this process' active window.
|
; 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.
|
; 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.
|
; 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)
|
ProcedureDLL Capture(*capRect.RECT, x.l, y.l, sx.l, sy.l)
|
||||||
Protected hWnd.l = GetProcHwnd()
|
Protected viewportRect.RECT
|
||||||
If Not hWnd
|
If Not GetRect(@viewportRect)
|
||||||
ProcedureReturn #False
|
ProcedureReturn #False
|
||||||
EndIf
|
EndIf
|
||||||
|
|
||||||
Protected rect.RECT
|
; Limit the desired capture area to the actual client area of the viewport.
|
||||||
If Not GetRect(@rect)
|
|
||||||
ProcedureReturn #False
|
|
||||||
EndIf
|
|
||||||
|
|
||||||
; Limit the desired capture area to the actual client area of the window.
|
|
||||||
If *capRect\left < 0 : *capRect\left = 0 : EndIf
|
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\top < 0 : *capRect\top = 0 : EndIf
|
||||||
If *capRect\bottom > rect\bottom-rect\top : *capRect\bottom = rect\bottom-rect\top : EndIf
|
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
|
||||||
|
|
||||||
imageID = CreateImage(#PB_Any, *capRect\right-*capRect\left, *capRect\bottom-*capRect\top)
|
Protected capWidth = *capRect\right - *capRect\left
|
||||||
|
Protected capHeight = *capRect\bottom - *capRect\top
|
||||||
|
|
||||||
|
imageID = CreateImage(#PB_Any, capWidth, capHeight)
|
||||||
If Not imageID
|
If Not imageID
|
||||||
ProcedureReturn #False
|
ProcedureReturn #False
|
||||||
EndIf
|
EndIf
|
||||||
|
|
||||||
; Get DC of window.
|
;Protected *pixelBuf = AllocateMemory(3 * width * height)
|
||||||
windowDC = GetDC_(hWnd)
|
|
||||||
If Not windowDC
|
|
||||||
FreeImage(imageID)
|
|
||||||
ProcedureReturn #False
|
|
||||||
EndIf
|
|
||||||
|
|
||||||
hDC = StartDrawing(ImageOutput(imageID))
|
hDC = StartDrawing(ImageOutput(imageID))
|
||||||
If Not hDC
|
If Not hDC
|
||||||
ReleaseDC_(hWnd, windowDC)
|
|
||||||
FreeImage(imageID)
|
FreeImage(imageID)
|
||||||
ProcedureReturn #False
|
ProcedureReturn #False
|
||||||
EndIf
|
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()
|
|
||||||
|
|
||||||
ReleaseDC_(hWnd, windowDC)
|
*pixelBuffer = DrawingBuffer()
|
||||||
|
glReadPixels_(*capRect\left, *capRect\top, capWidth, capHeight, #GL_BGR_EXT, #GL_UNSIGNED_BYTE, *pixelBuffer)
|
||||||
|
|
||||||
|
;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)
|
LockMutex(Mutex)
|
||||||
; Check if the queue has too many elements, if so, wait. (Emulate go's channels)
|
; Check if the queue has too many elements, if so, wait. (Emulate go's channels)
|
||||||
@ -173,13 +169,13 @@ EndProcedure
|
|||||||
;Capture(123, 123)
|
;Capture(123, 123)
|
||||||
;Delay(1000)
|
;Delay(1000)
|
||||||
|
|
||||||
; IDE Options = PureBasic 6.00 LTS (Windows - x64)
|
; IDE Options = PureBasic 6.04 LTS (Windows - x64)
|
||||||
; ExecutableFormat = Shared dll
|
; ExecutableFormat = Shared dll
|
||||||
; CursorPosition = 94
|
; CursorPosition = 126
|
||||||
; FirstLine = 39
|
; FirstLine = 98
|
||||||
; Folding = --
|
; Folding = -
|
||||||
; Optimizer
|
; Optimizer
|
||||||
; EnableThread
|
; EnableThread
|
||||||
; EnableXP
|
; EnableXP
|
||||||
; Executable = capture.dll
|
; 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
|
end
|
||||||
|
|
||||||
local rectTopLeft, rectBottomRight = ScreenCapture.GetRect()
|
local rectTopLeft, rectBottomRight = ScreenCapture.GetRect()
|
||||||
if Coords.WindowResolution ~= rectBottomRight - rectTopLeft then
|
if Coords:InternalRectSize() ~= rectBottomRight - rectTopLeft then
|
||||||
error(string.format("window size seems to have changed from %s to %s", Coords.WindowResolution, rectBottomRight - rectTopLeft))
|
error(string.format("internal rectangle size seems to have changed from %s to %s", Coords:InternalRectSize(), rectBottomRight - rectTopLeft))
|
||||||
end
|
end
|
||||||
|
|
||||||
local topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
|
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.
|
-- Suspend UI drawing for 1 frame.
|
||||||
UI:SuspendDrawing(1)
|
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
|
if not pos then
|
||||||
topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
|
topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
|
||||||
if outputPixelScale > 0 then
|
if outputPixelScale > 0 then
|
||||||
@ -120,6 +120,9 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Wait for two frames.
|
||||||
|
wait(1)
|
||||||
|
|
||||||
-- The top left world position needs to be upscaled by the pixel scale.
|
-- The top left world position needs to be upscaled by the pixel scale.
|
||||||
-- Otherwise it's not possible to stitch the images correctly.
|
-- Otherwise it's not possible to stitch the images correctly.
|
||||||
if not ScreenCapture.Capture(topLeftCapture, bottomRightCapture, outputTopLeft, (bottomRightWorld - topLeftWorld) * outputPixelScale) then
|
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.
|
-- This software is released under the MIT License.
|
||||||
-- https://opensource.org/licenses/MIT
|
-- https://opensource.org/licenses/MIT
|
||||||
@ -53,8 +53,8 @@ function Check:Regular(interval)
|
|||||||
local topLeft, bottomRight = ScreenCap.GetRect() -- Actual window client area.
|
local topLeft, bottomRight = ScreenCap.GetRect() -- Actual window client area.
|
||||||
if topLeft and bottomRight then
|
if topLeft and bottomRight then
|
||||||
local actual = bottomRight - topLeft
|
local actual = bottomRight - topLeft
|
||||||
if actual ~= Coords.WindowResolution then
|
if actual ~= Coords:InternalRectSize() then
|
||||||
Message:ShowWrongResolution(Modification.AutoSet, string.format("Old window resolution is %s. Current resolution is %s.", Coords.WindowResolution, actual))
|
Message:ShowWrongResolution(Modification.AutoSet, string.format("Internal rectangle size is %s. Current resolution is %s.", Coords:InternalRectSize(), actual))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Message:ShowRuntimeError("GetRect", "Couldn't determine window resolution.")
|
Message:ShowRuntimeError("GetRect", "Couldn't determine window resolution.")
|
||||||
@ -62,12 +62,12 @@ function Check:Regular(interval)
|
|||||||
|
|
||||||
-- Check if we have the required settings.
|
-- Check if we have the required settings.
|
||||||
local config, magic, patches = Modification.RequiredChanges()
|
local config, magic, patches = Modification.RequiredChanges()
|
||||||
if config["fullscreen"] then
|
--if config["fullscreen"] then
|
||||||
local expected = tonumber(config["fullscreen"])
|
-- local expected = tonumber(config["fullscreen"])
|
||||||
if expected ~= Coords.FullscreenMode then
|
-- if expected ~= Coords.FullscreenMode then
|
||||||
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Fullscreen mode %s. Expected %s.", Coords.FullscreenMode, expected))
|
-- Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Fullscreen mode %s. Expected %s.", Coords.FullscreenMode, expected))
|
||||||
end
|
-- end
|
||||||
end
|
--end
|
||||||
if config["window_w"] and config["window_h"] then
|
if config["window_w"] and config["window_h"] then
|
||||||
local expected = Vec2(tonumber(config["window_w"]), tonumber(config["window_h"]))
|
local expected = Vec2(tonumber(config["window_w"]), tonumber(config["window_h"]))
|
||||||
if expected ~= Coords.WindowResolution then
|
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.
|
-- This software is released under the MIT License.
|
||||||
-- https://opensource.org/licenses/MIT
|
-- https://opensource.org/licenses/MIT
|
||||||
@ -24,6 +24,13 @@ ffi.cdef([[
|
|||||||
LONG bottom;
|
LONG bottom;
|
||||||
} RECT;
|
} RECT;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LONG x;
|
||||||
|
LONG y;
|
||||||
|
LONG width;
|
||||||
|
LONG height;
|
||||||
|
} GLViewportDims;
|
||||||
|
|
||||||
bool GetRect(RECT* rect);
|
bool GetRect(RECT* rect);
|
||||||
bool Capture(RECT* rect, int x, int y, int sx, int sy);
|
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.
|
---Called *every* time the game is about to start updating the world.
|
||||||
function OnWorldPreUpdate()
|
function OnWorldPreUpdate()
|
||||||
Message:CatchException("OnWorldPreUpdate", function()
|
Message:CatchException("OnWorldPreUpdate", function()
|
||||||
|
|
||||||
-- Coroutines aren't run every frame in this lua sandbox, do it manually here.
|
-- 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)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Called *every* time the game has finished updating the world.
|
---Called *every* time the game has finished updating the world.
|
||||||
function OnWorldPostUpdate()
|
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()
|
Message:CatchException("OnWorldPostUpdate", function()
|
||||||
-- Reload mod every 60 frames.
|
-- Reload mod every 60 frames.
|
||||||
-- This allows live updates to the mod while Noita is running.
|
-- This allows live updates to the mod while Noita is running.
|
||||||
|
Loading…
Reference in New Issue
Block a user