Skip to content

Commit

Permalink
Add support for asynchronously screenshot capturing.
Browse files Browse the repository at this point in the history
  • Loading branch information
hzqst committed Jan 31, 2024
1 parent c46c387 commit e7a4492
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 45 deletions.
8 changes: 5 additions & 3 deletions Plugins/SteamScreenshots/SteamScreenshots.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global_template.props" Condition="!exists('$(SolutionDir)tools\global.props') and exists('$(SolutionDir)tools\global_template.props')" />
<Import Project="$(SolutionDir)tools\global_common.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release_AVX2|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global_template.props" Condition="!exists('$(SolutionDir)tools\global.props') and exists('$(SolutionDir)tools\global_template.props')" />
<Import Project="$(SolutionDir)tools\global_common.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global.props" Condition="exists('$(SolutionDir)tools\global.props')" />
<Import Project="$(SolutionDir)tools\global_template.props" Condition="!exists('$(SolutionDir)tools\global.props') and exists('$(SolutionDir)tools\global_template.props')" />
<Import Project="$(SolutionDir)tools\global_common.props" />
</ImportGroup>
Expand Down Expand Up @@ -160,10 +160,12 @@
<ClCompile Include="..\..\include\HLSDK\common\parsemsg.cpp" />
<ClCompile Include="exportfuncs.cpp" />
<ClCompile Include="plugins.cpp" />
<ClCompile Include="gl_catpure.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="exportfuncs.h" />
<ClInclude Include="plugins.h" />
<ClInclude Include="gl_capture.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
6 changes: 6 additions & 0 deletions Plugins/SteamScreenshots/SteamScreenshots.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<ClCompile Include="..\..\include\HLSDK\common\parsemsg.cpp">
<Filter>HLSDK</Filter>
</ClCompile>
<ClCompile Include="gl_catpure.cpp">
<Filter>Source</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="exportfuncs.h">
Expand All @@ -34,5 +37,8 @@
<ClInclude Include="plugins.h">
<Filter>Header</Filter>
</ClInclude>
<ClInclude Include="gl_capture.h">
<Filter>Header</Filter>
</ClInclude>
</ItemGroup>
</Project>
68 changes: 29 additions & 39 deletions Plugins/SteamScreenshots/exportfuncs.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
#include <metahook.h>
#include <glew.h>
#include <studio.h>
#include <r_studioint.h>
#include "cl_entity.h"
#include "com_model.h"
#include "triangleapi.h"
#include "cvardef.h"
#include "exportfuncs.h"
#include "entity_types.h"
#include "parsemsg.h"
#include "gl_capture.h"
#include <steam_api.h>

cl_enginefunc_t gEngfuncs;
Expand All @@ -25,33 +21,6 @@ class CSnapshotManager

CSnapshotManager g_SnapshotManager;

void VID_Snapshot_f(void)
{
int glwidth, glheight;
g_pMetaHookAPI->GetVideoMode(&glwidth, &glheight, NULL, NULL);

byte *pBuf = (byte *)malloc(glwidth * glheight * 3);

int read_fbo = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glReadPixels(0, 0, glwidth, glheight, GL_RGB, GL_UNSIGNED_BYTE, pBuf);
glBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo);

for (int y = 0; y < glheight / 2; ++y) {
for (int x = 0; x < glwidth; ++x) {
byte temp[3];
memcpy(temp, &pBuf[(glheight - y - 1) * glwidth * 3 + x * 3], 3);
memcpy(&pBuf[(glheight - y - 1) * glwidth * 3 + x * 3], &pBuf[y * glwidth * 3 + x * 3], 3);
memcpy(&pBuf[y * glwidth * 3 + x * 3], temp, 3);
}
}

SteamScreenshots()->WriteScreenshot(pBuf, glwidth * glheight * 3, glwidth, glheight);

free(pBuf);
}

void CSnapshotManager::OnSnapshotCallback(ScreenshotReady_t* pCallback)
{
if (pCallback->m_eResult == k_EResultOK)
Expand All @@ -60,32 +29,50 @@ void CSnapshotManager::OnSnapshotCallback(ScreenshotReady_t* pCallback)

SteamScreenshots()->TagUser(pCallback->m_hLocal, SteamUser()->GetSteamID());

gEngfuncs.Con_Printf("Snapshot saved.\n");
gEngfuncs.Con_Printf("[SteamScreenshots] Snapshot saved.\n");
}
else if (pCallback->m_eResult == k_EResultFail)
else if (pCallback->m_eResult == k_EResultIOFailure)
{
gEngfuncs.Con_Printf("Error: Cannot parse snapshot.\n");
gEngfuncs.Con_Printf("[SteamScreenshots] Cannot save snapshot. Got an IO error.\n");
}
else if (pCallback->m_eResult == k_EResultIOFailure)
else
{
gEngfuncs.Con_Printf("Error: Cannot save snapshot.\n");
gEngfuncs.Con_Printf("[SteamScreenshots] Cannot save snapshot. Got an unknown error.\n");
}
}

void HUD_Shutdown(void)
{
GL_ShutdownCapture();

gExportfuncs.HUD_Shutdown();
}

void ScreenshotCallback(void* pBuf, size_t cbBufSize, int width, int height)
{
SteamScreenshots()->WriteScreenshot(pBuf, cbBufSize, width, height);
}

void VID_Snapshot_f(void)
{
GL_BeginCapture(ScreenshotCallback);
}

void HUD_Frame(double time)
{
gExportfuncs.HUD_Frame(time);

auto levelname = gEngfuncs.pfnGetLevelName();

if (!levelname || !levelname[0])
{
g_szServerName[0] = 0;
}

//SteamAPI_RunCallbacks();
GL_QueryAsyncCapture(ScreenshotCallback);
}

pfnUserMsgHook m_pfnServerName;
pfnUserMsgHook m_pfnServerName = NULL;

int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf)
{
Expand All @@ -107,11 +94,14 @@ void IN_ActivateMouse(void)

if (!init)
{
GL_InitCapture();

//cmd "snapshot" is registered after HUD_Init

g_pMetaHookAPI->HookCmd("snapshot", VID_Snapshot_f);

m_pfnServerName = HOOK_MESSAGE(ServerName);

init = true;
}
}
3 changes: 2 additions & 1 deletion Plugins/SteamScreenshots/exportfuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
void IN_ActivateMouse(void);
void HUD_Init(void);
void HUD_StudioEvent(const struct mstudioevent_s *ev, const struct cl_entity_s *ent);
void HUD_Frame(double time);
void HUD_Frame(double time);
void HUD_Shutdown(void);
8 changes: 8 additions & 0 deletions Plugins/SteamScreenshots/gl_capture.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

typedef void (*fnGLQueryCaptureCallback)(void* pBuf, size_t cbBufSize, int width, int height);

void GL_InitCapture();
void GL_ShutdownCapture();
void GL_BeginCapture(fnGLQueryCaptureCallback callback);
void GL_QueryAsyncCapture(fnGLQueryCaptureCallback callback);
192 changes: 192 additions & 0 deletions Plugins/SteamScreenshots/gl_catpure.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#include <metahook.h>
#include <glew.h>
#include "gl_capture.h"

GLuint g_CapturePBO = 0;
GLsync g_CaptureSyncObject = 0;
int g_CaptureImageWidth = 0;
int g_CaptureImageHeight = 0;
void* g_CaptureImageBuffer = NULL;
void (*g_pfnBeginCapture)(fnGLQueryCaptureCallback callback) = NULL;

void GL_InitCaptureImageBuffer(int width, int height)
{
g_CaptureImageBuffer = (byte*)malloc(width * height * 3);
}

void GL_ShutdownCaptureImageBuffer()
{
if (g_CaptureImageBuffer)
{
free(g_CaptureImageBuffer);
g_CaptureImageBuffer = NULL;
}
}

void GL_InitCapturePBO(int width, int height)
{
glGenBuffers(1, &g_CapturePBO);
glBindBuffer(GL_PIXEL_PACK_BUFFER, g_CapturePBO);
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 3, 0, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

void GL_ShutdownCapturePBO()
{
if (g_CapturePBO)
{
glDeleteBuffers(1, &g_CapturePBO);
g_CapturePBO = 0;
}
}

void GL_ShutdownCaptureSyncObject()
{
if (g_CaptureSyncObject)
{
glDeleteSync(g_CaptureSyncObject);
g_CaptureSyncObject = 0;
}
}

void GL_BeginSyncCapture(fnGLQueryCaptureCallback callback)
{
int glwidth, glheight;
g_pMetaHookAPI->GetVideoMode(&glwidth, &glheight, NULL, NULL);

if (glwidth != g_CaptureImageWidth || glheight != g_CaptureImageHeight)
{
GL_ShutdownCaptureImageBuffer();
GL_InitCaptureImageBuffer(glwidth, glheight);

g_CaptureImageWidth = glwidth;
g_CaptureImageHeight = glheight;
}

int originalFBO = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &originalFBO);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

glReadPixels(0, 0, g_CaptureImageWidth, g_CaptureImageHeight, GL_RGB, GL_UNSIGNED_BYTE, g_CaptureImageBuffer);

glBindFramebuffer(GL_READ_FRAMEBUFFER, originalFBO);

GLubyte* pBuf = (GLubyte*)g_CaptureImageBuffer;

//Flip the image up-side down
for (int y = 0; y < g_CaptureImageHeight / 2; ++y) {
for (int x = 0; x < g_CaptureImageWidth; ++x) {
byte temp[3];
memcpy(temp, &pBuf[(g_CaptureImageHeight - y - 1) * g_CaptureImageWidth * 3 + x * 3], 3);
memcpy(&pBuf[(g_CaptureImageHeight - y - 1) * g_CaptureImageWidth * 3 + x * 3], &pBuf[y * g_CaptureImageWidth * 3 + x * 3], 3);
memcpy(&pBuf[y * g_CaptureImageWidth * 3 + x * 3], temp, 3);
}
}

callback(g_CaptureImageBuffer, g_CaptureImageWidth * g_CaptureImageHeight * 3, g_CaptureImageWidth, g_CaptureImageHeight);
}

void GL_BeginAsyncCapture(fnGLQueryCaptureCallback callback)
{
if (g_CaptureSyncObject)
return;

int glwidth, glheight;
g_pMetaHookAPI->GetVideoMode(&glwidth, &glheight, NULL, NULL);

if (glwidth != g_CaptureImageWidth || glheight != g_CaptureImageHeight)
{
GL_ShutdownCaptureImageBuffer();
GL_ShutdownCapturePBO();
GL_InitCapturePBO(glwidth, glheight);
GL_InitCaptureImageBuffer(glwidth, glheight);

g_CaptureImageWidth = glwidth;
g_CaptureImageHeight = glheight;
}

int originalFBO = 0;
int originalPBO = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &originalFBO);
glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &originalPBO);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, g_CapturePBO);

glReadPixels(0, 0, g_CaptureImageWidth, g_CaptureImageHeight, GL_RGB, GL_UNSIGNED_BYTE, 0);

g_CaptureSyncObject = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

glBindBuffer(GL_PIXEL_PACK_BUFFER, originalPBO);
glBindFramebuffer(GL_READ_FRAMEBUFFER, originalFBO);
}

void GL_BeginCapture(fnGLQueryCaptureCallback callback)
{
return g_pfnBeginCapture(callback);
}

void GL_QueryAsyncCapture(fnGLQueryCaptureCallback callback)
{
if (!g_CaptureSyncObject)
return;

GLenum wait = glClientWaitSync(g_CaptureSyncObject, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
if (wait == GL_ALREADY_SIGNALED || wait == GL_CONDITION_SATISFIED)
{
int originalPBO = 0;
glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &originalPBO);

glBindBuffer(GL_PIXEL_PACK_BUFFER, g_CapturePBO);

GLubyte* ptr = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (ptr)
{
memcpy(g_CaptureImageBuffer, ptr, g_CaptureImageWidth * g_CaptureImageHeight * 3);

GLubyte* pBuf = (GLubyte*)g_CaptureImageBuffer;

//Flip the image up-side down
for (int y = 0; y < g_CaptureImageHeight / 2; ++y) {
for (int x = 0; x < g_CaptureImageWidth; ++x) {
byte temp[3];
memcpy(temp, &pBuf[(g_CaptureImageHeight - y - 1) * g_CaptureImageWidth * 3 + x * 3], 3);
memcpy(&pBuf[(g_CaptureImageHeight - y - 1) * g_CaptureImageWidth * 3 + x * 3], &pBuf[y * g_CaptureImageWidth * 3 + x * 3], 3);
memcpy(&pBuf[y * g_CaptureImageWidth * 3 + x * 3], temp, 3);
}
}

callback(g_CaptureImageBuffer, g_CaptureImageWidth * g_CaptureImageHeight * 3, g_CaptureImageWidth, g_CaptureImageHeight);

glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}

glBindBuffer(GL_PIXEL_PACK_BUFFER, originalPBO);

glDeleteSync(g_CaptureSyncObject);
g_CaptureSyncObject = 0;
}
}

void GL_ShutdownCapture()
{
GL_ShutdownCaptureImageBuffer();
GL_ShutdownCapturePBO();
GL_ShutdownCaptureSyncObject();
}

void GL_InitCapture()
{
g_pMetaHookAPI->GetVideoMode(&g_CaptureImageWidth, &g_CaptureImageHeight, NULL, NULL);

if (GLEW_VERSION_3_2)
{
GL_InitCapturePBO(g_CaptureImageWidth, g_CaptureImageHeight);
g_pfnBeginCapture = GL_BeginAsyncCapture;
}
else
{
g_pfnBeginCapture = GL_BeginSyncCapture;
}

GL_InitCaptureImageBuffer(g_CaptureImageWidth, g_CaptureImageHeight);
}
Loading

0 comments on commit e7a4492

Please sign in to comment.