✅ Quicker Screenshot
I'm trying to quickly capture a screenshot. My current method (when capturing the entirety of a 3440x1440p screen) takes about 60-80ms:
Capturing a smaller region of the screen (via
bounds
) definitely helps. Thanks!!135 Replies
What are you making?
60-80ms
for a 3440x1440p
screen does not sound that bad?
Maybe limit it to a certain resolution instead of depending on what is possibleGDI is horribly slow. try the Desktop Duplication API its vsynced (so its as fast as it has to be)
thr only problem is you will need to pinvoke it into c# yourself or find a library that already did that
Desktop Duplication API - Win32 apps
Windows 8 disables standard Windows 2000 Display Driver Model (XDDM) mirror drivers and offers the desktop duplication API instead.
A personal project for image detection (doesn't run continuously) that I'm trying to better optimize
Any chance you know of a good C# wrapper for it? I'm still a noob with C# (and way more so C++) and this is a great learning opportunity.
directx would probably be the fastest
super high speed isn't absolutely necessary (this isn't a live screen capture application)
just trying to keep things (relatively) lightweight but trying to see what the next level of "better" is
yeah thats the desktop duplication api for
@Exergist i think ive found something you could use. https://github.com/nnn149/DesktopDuplicationWapper
GitHub
GitHub - nnn149/DesktopDuplicationWapper: C# wrapper for the Deskto...
C# wrapper for the Desktop Duplication Api. Contribute to nnn149/DesktopDuplicationWapper development by creating an account on GitHub.
its a wrapper on top of a wrapper on top of a wrapper of a wrapper
oh its looks like its a wrapper that uses directx
I saw firstly 'reference' in my more than four years programming experience
Unfortunately one of the DesktopDuplicationWrapper dependencies (Vortice.Direct3D11) has an issue when installing via NuGet
Unable to find a version of 'SharpGen.Runtime' that is compatible with: 'Vortice.Direct3D11 2.2.0 constraint: SharpGen.Runtime (>= 2.0.0-beta.11)', 'Vortice.DirectX 2.2.0 constraint: SharpGen.Runtime (>= 2.0.0-beta.11)', 'Vortice.DXGI 2.2.0 constraint: SharpGen.Runtime (>= 2.0.0-beta.11)'
I submitted an issue for the repo, but I'm not sure what to do from here 🤔
It looks like the available NuGet packages for SharpGen.Runtime and SharpGen are at v1.2.1 and v1.2.0 respectively.
Any recommendations?get the preview? thats a checkbox somewhere in thr nuget download screen
OK I'll check for it tonight
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.After following the suggestion by R'tsej and doing some more fiddling with pre-installing required dependencies via NuGet I finally got the
DesktopDuplicationWapper
components installed. Unfortunately upon executing the sample code the captured content is just a black image, so I'm going to submit an issue to that repo.In the mean time I'll probably give this version a try https://github.com/nicolasdeory/desktop-duplication-net
GitHub
GitHub - nicolasdeory/desktop-duplication-net: Capture the desktop ...
Capture the desktop in real time with .NET Core using the Windows 8 Desktop Duplication API - GitHub - nicolasdeory/desktop-duplication-net: Capture the desktop in real time with .NET Core using th...
i might have some code lying around, ill take a look for you
unfortunately I get the same behavior (black image) with the
desktop-duplication-net
repo (though I also realized that I need to target .NET Framework, so I can't use it anyway)black image? thats weird <:PES_Think:639363477458255874>
can i see your code?
@Jester
Without that small pause I get a black image 90% of the time. To be clear, I'm back to using
DesktopDuplicationWapper
.that means you have a sync issue
@Exergist im pretty sure the second frame you get will always be fine. its the very first frame thats often black in this api, ive noticed
thread.sleep is never the solution
yeah remove the sleep
only the first frame will be black, all the others will work fine
in my code using it i dont use the very first frame either, but i do use every one after that
i think the api always syncs automatically
oh idk why then
This seems to work more reliably. Though at the time of testing
frame1
is delivering an image as expected 🤔i think bcuz it gets the frames from the DWM and the DWM doesnt always keep an image ready for this api until someone uses it? <:PES_Think:639363477458255874>
So with the following:
The "screenshot time" for a 1080p desktop is averaging about 250-300 ms, which is slower than the method I originally posted.
I'm guessing that's not expected?
with or without saving it?
it would be weird if getframe would take more than 16ms on a 60fps display
above code encapsulates the timing range (no image save)
that's why i figured something was amiss
rewrite it in C++ you create an adapter, then an output and device and devicecontext with it
id write a wrapper for this in c++ https://github.com/Microsoft/DirectXTK/wiki/ScreenGrab
GitHub
ScreenGrab · microsoft/DirectXTK Wiki
The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++ - ScreenGrab · microsoft/DirectXTK Wiki
with that output an output duplocator
and then you make a few textures2ds
blablabla
i could change some of my code to make a sketchy c# wrapper for it
O_o
lol that's probably out of scope for now
I'd always be open to checking it out, but I don't want you to sink time into it unless you're keen on it.
I figured there might be a low friction method to reducing screen capture time below ~60-80ms, but it seems like more investment would be needed.
TLDR - if you really want to make a sketchy wrapper I'd certainly check it out, otherwise what I have right now is probably sufficient.
@Exergist here is the wrapper i made. it works perfectly on my machine. i get a frame every 1-16ms. and it doesnt need any nuget packages. (also the first frame isnt black and there are probably way less allocations than other wrappers out there) https://github.com/QubitTooLate/Snippies/blob/main/DesktopDuplicator.cs
GitHub
Snippies/DesktopDuplicator.cs at main · QubitTooLate/Snippies
A bunch of snippets, tips and tricks I learnt during my projects. - Snippies/DesktopDuplicator.cs at main · QubitTooLate/Snippies
Awesome!!!
Ah but I'm limited to .Net Framework (C# 7.3)
hmmm trying to get this to work
https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb
Medium
Enabling and using C# 9 features on older and “unsupported” runtime...
A guide on how to enable support for many new C# 9 features on older runtimes and frameworks that do not offer them out of the box
Wait,
i'm developing this as a plugin to run within the process of another app, which requires .Net Framework for interface
Okay this resolved a bunch of errors 🙂
<LangVersion>9.0</LangVersion>
@Jester all that remains is this error
I must be missing something
you just need a way to have some unmanaged or memory
there are plenty other ways to do it
let me look
Marshal.AllocHGlobal Method (System.Runtime.InteropServices)
Allocates memory from the unmanaged memory of the process.
@Exergist try using this instead
how is it going?
sorry had to step away for a second. i'm poking through and trying to swap for Marshal
yep I'm struggling with this one. apologies, I'm not familiar with directly handling memory 😕
whats the issue?
replace alloc
frameBuffer = Marshal.AllocHGlobal(description.Width * 4 * description.Height).ToPointer();
replace free
Marshal.FreeHGlobal(new IntPtr(frameBuffer));
getting close!
no
ToPointer
for uint
uint?
a ( too many
on
brb, catching a train. appreciate the incredible help here
you should be able to figure it out yourself but here i already gave the answer
wrong chat
ah yes you did! looks like this does the job
frameBuffer = (void*)Marshal.AllocHGlobal(new IntPtr(description.Width * 4 * description.Height));
🤔
uh oh
fails this assertion
looks like you dont have some part of directx installed or windows sdk or something
idk
bwa?
idk but it might work without the debug flag
looks like the image gets disposed?
bitmap
contains something that is 1080p, but test
doesn't receive anything (and no image gets saved)
wait nvm
Looks like I cannot apply using
to the bitmap
But removing it works! I'll do some more testing tonight. Thank you everyone for your continued support!
@Jester awesome snippetthanks
<:PES_Happy:493353112493621258>
yeah returning a disposed bitmap isnt very useful ofc
Any suggestions for how i can monitor for memory leaks?
i honestly dont now. just write code that doesnt leak you know
Lol well yea. I meant within the confines of this excellent snippet
In the past I've done something along these lines https://www.codeproject.com/Articles/42721/Best-Practices-No-5-Detecting-NET-application-memo
CodeProject
Best Practices No. 5: Detecting .NET application memory leaks
In this article we are going to detect .NET application memory leaks.
@Jester yesterday I was doing my testing of your snippet via a trio of 1080p monitors (with one of them being the main target monitor for duplication).
Today I tried it via three 1080p monitors and one 3440x1440 monitor (the 3440 is the main target monitor). Mentioning all this because today I encountered this error within the
WriteFrameIntoBuffer
method.<:PES_Think:639363477458255874>
when i target the 1080p monitors the issue disappears
id like to see the values of width, height and rowpitch
standby
3440p target monitor
1080p target monitor
updated to include buffer
in reality my current setup has:
- one 3440x1440p
- one 3840x2160p
- two 1920x1080p
I just happen to run the 4K monitor at 1080p while working. I tested the 4K monitor while running at 2560x1440p and that works fine.
I then tried my 4K monitor running at 1680x1050 (16:10 ratio) and that does NOT work. I know very little about this area, but perhaps there's an issue with non-16:9 display ratios?
it could be
maybe my pitch assumption is wrong
yeah my code must have a few mistakes
i wrote it half asleep
do i even use my framebuffer field?
<:PES_HuhWtf:642742545700618303>
i do not so that can even be removed
ill see if i can improve it in a few hours
Game Development Stack Exchange
Determine the stride of a DirectX Texture2D line?
Is there a way to determine, or preferably calculate/predict, the the stride of a line of a DirectX 11 Texture2D resource when using SharpDX?
(E.g. Can we say the stride of a line is always a powe...
hmm this sucks
but maybe if i do this once in the constructor i can assume it will be the same for every following frame
seriously @Jester don't lose sleep over this. the GDI method is FINE
thus removing the need for allocations
i want to use my snippet myself too
ok then i'm onboard to help test along the way 😉
i would like a fast remote desktop for at home but its not as good as the nvidia one
also (at least for me) when i compare total bitmap capture time for DesktopDuplicator vs GDI the GDI is slightly faster (maybe by 20-40ms)
granted i'm no pro at properly setting up "fair" performance tests
im not sure if he buffer that gets written to should be pinned or not
i do not intend to spam this, but i'm just looking for leaks
i can believe the first frame being slower
again, don't apply a ton of weight to my feedback
on my end the VS process memory definitely accumulates when running DesktopDuplicator 30 times
nope it accumulates on my GDI version....
do you free the framebuffer in the dispose?
for DesktopDuplication yes?
yes
I'll say yes
lgtm
dont allocate a new instance of this class for every screenshot. but i believe you really only needed one?
again my earlier comment was about MY GDI screenshot method
memory seems fine for the DesktopDuplication
the gdi graphics.copyfromscreen ?
or something
si
oh the gdi method leaks?
not mine?
seemingly yes
yours seems fine
my class didnt work for you at first, how did you fix that error?
which error specifically?
DesktopDuplication still doesn't work for non-16:9 displays. I've just been continuing work by targeting my 1080p display.
this one
yes, got rid of the debug bits
the memory pitch is different for other ratio displays for gpu memory optimizations and i didnt think about that
for example in hlsl shaders everything has to be like a multiple of 16 bits or bytes i think
i do know the gdi api by heart as well
getdc
createcompatibledc
createcompatiblebitmap
selectobject
bitblt
getdibits
releasedc
deletedc
deleteobj
@Exergist i have pushed a fixed version of the code
if you init the class when the application starts then you dont have to wait so long in the screenshot method itself
sweet i'll give that a try now 🙂
hmm i encountered a memory access issue that persists between runs. i'm going to restart and try again.
@Jester ok so this seems to work fine for 1080p (though I did get an example where the image was all black).
Also works fine for 1440p screen.
However when I target my 3440x1440p screen i CAN obtain the bitmap and save it, however upon further processing of the image (which again, works fine at 1080p and 1440p) I receive an error.
<a:aPES_CryDrink:678210605781352448>
idk whats wrong
It's most interesting that the issue is limited to a particular screen (or size?)
I'm afk now but IIRC I can switch target screen around and there's no issue. Later I can also try different resolution types via my 4k monitor if that would be helpful.
but the issue is outside of my snippet now? all my code works fine? except for the possible black screen?
Everything works fine with both versions except for 3440x1440p (though IIRC the errors were different between versions)
And occasionally I get a black screen when it does work
where do you get the access error?
Doing some opencvsharp Mat processing
oh ok
then my code works fine
It can handle a saved 3440p bitmap fine, but not one from the screenshot repo you shared
<:PES_Think:639363477458255874>
the bitmap object?
or just the bytes?
weird if the bitmap object does work in one case but not the other
can i see the code of you passing the bitmap to the opencv?
I can share later tonight
First I should point out I removed the
using
so that bitmap isn't disposed before I can actually use itSecond I had to once again remove the debug parts to get rid of a runtime error
GetDuplicationScreenshot
calls your snippet. The error occurs at mat = BitmapConverter.ToMat(screenshot)
. I AM able to save test.png
.(for some reason Discord won't let me enter this)
@Jester well.....it's working now. Not sure what did it but i'm no longer getting errors O_o
Though I will say that I do still occasionally get a black screen
Also I found that the memory error I mentioned previously only occurs when I hit the
mat = BitmapConverter.ToMat(screenshot)
line while debuggingSo TLDR - things seem to work fine now that I've made this change (I think there was some kind of bitmap sharing issue between the snippet and OpenCvSharp, so doing the CV work on a NEW version of the snippet bitmap did the trick)
BUT I do still receive black screens occasionally
Doing an "is the bitmap all black" check costs about 30-40 ms, which I might be willing to pay if the occasional black images are not avoidable (https://stackoverflow.com/questions/2556447/whats-an-efficient-way-to-tell-if-a-bitmap-is-entirely-black/2556571#2556571)
Stack Overflow
What's an efficient way to tell if a bitmap is entirely black?
I'm wondering if there's a super-efficient way of confirming that an Image object references an entirely black image, so every pixel within the bitmap is ARGB(255, 0, 0, 0).
What would you recomme...
I also noticed I'm way more likely to get a black image if my mouse cursor is moving at the time of screen capture
<:PES_Think:639363477458255874>
I previously encountered the access violation depending on how I attempted to use the bitmap after it was created (I only explored this at 3440p). I was able to work around it via trial and error but I never figured out the root cause.
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.Well @Jester what you've provided has proven to be super helpful. Thank you very much!!