mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-11 07:12:02 +00:00
- consolidated the savegame picture code.
This commit is contained in:
parent
6177ed153d
commit
44d39ef63e
11 changed files with 158 additions and 177 deletions
|
@ -82,6 +82,7 @@
|
|||
#include "events.h"
|
||||
#include "c_buttons.h"
|
||||
#include "d_buttons.h"
|
||||
#include "hwrenderer/scene/hw_drawinfo.h"
|
||||
|
||||
|
||||
static FRandom pr_dmspawn ("DMSpawn");
|
||||
|
@ -2252,56 +2253,6 @@ static void PutSaveComment (FSerializer &arc)
|
|||
arc.AddString("Comment", comment);
|
||||
}
|
||||
|
||||
void DoWriteSavePic(FileWriter *file, ESSType ssformat, uint8_t *scr, int width, int height, sector_t *viewsector, bool upsidedown)
|
||||
{
|
||||
PalEntry palette[256];
|
||||
PalEntry modulateColor;
|
||||
auto blend = V_CalcBlend(viewsector, &modulateColor);
|
||||
int pixelsize = 1;
|
||||
// Apply the screen blend, because the renderer does not provide this.
|
||||
if (ssformat == SS_RGB)
|
||||
{
|
||||
int numbytes = width * height * 3;
|
||||
pixelsize = 3;
|
||||
if (modulateColor != 0xffffffff)
|
||||
{
|
||||
float r = modulateColor.r / 255.f;
|
||||
float g = modulateColor.g / 255.f;
|
||||
float b = modulateColor.b / 255.f;
|
||||
for (int i = 0; i < numbytes; i += 3)
|
||||
{
|
||||
scr[i] = uint8_t(scr[i] * r);
|
||||
scr[i + 1] = uint8_t(scr[i + 1] * g);
|
||||
scr[i + 2] = uint8_t(scr[i + 2] * b);
|
||||
}
|
||||
}
|
||||
float iblendfac = 1.f - blend.W;
|
||||
blend.X *= blend.W;
|
||||
blend.Y *= blend.W;
|
||||
blend.Z *= blend.W;
|
||||
for (int i = 0; i < numbytes; i += 3)
|
||||
{
|
||||
scr[i] = uint8_t(scr[i] * iblendfac + blend.X);
|
||||
scr[i + 1] = uint8_t(scr[i + 1] * iblendfac + blend.Y);
|
||||
scr[i + 2] = uint8_t(scr[i + 2] * iblendfac + blend.Z);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply the screen blend to the palette. The colormap related parts get skipped here because these are already part of the image.
|
||||
DoBlending(GPalette.BaseColors, palette, 256, uint8_t(blend.X), uint8_t(blend.Y), uint8_t(blend.Z), uint8_t(blend.W*255));
|
||||
}
|
||||
|
||||
int pitch = width * pixelsize;
|
||||
if (upsidedown)
|
||||
{
|
||||
scr += ((height - 1) * width * pixelsize);
|
||||
pitch *= -1;
|
||||
}
|
||||
|
||||
M_CreatePNG(file, scr, ssformat == SS_PAL? palette : nullptr, ssformat, width, height, pitch, Gamma);
|
||||
}
|
||||
|
||||
static void PutSavePic (FileWriter *file, int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0 || !storesavepic)
|
||||
|
@ -2312,7 +2263,7 @@ static void PutSavePic (FileWriter *file, int width, int height)
|
|||
{
|
||||
D_Render([&]()
|
||||
{
|
||||
screen->WriteSavePic(&players[consoleplayer], file, width, height);
|
||||
WriteSavePic(&players[consoleplayer], file, width, height);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "r_data/r_interpolate.h"
|
||||
#include "r_utility.h"
|
||||
#include "d_player.h"
|
||||
#include "swrenderer/r_renderer.h"
|
||||
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
||||
#include "hwrenderer/utility/hw_clock.h"
|
||||
#include "hwrenderer/data/flatvertices.h"
|
||||
|
@ -39,6 +40,7 @@
|
|||
#include "hwrenderer/dynlights/hw_lightbuffer.h"
|
||||
#include "hwrenderer/utility/hw_cvars.h"
|
||||
#include "hwrenderer/data/hw_viewpointbuffer.h"
|
||||
#include "hwrenderer/scene/hw_fakeflat.h"
|
||||
#include "hwrenderer/scene/hw_clipper.h"
|
||||
#include "hwrenderer/scene/hw_portal.h"
|
||||
#include "hwrenderer/utility/hw_vrmodes.h"
|
||||
|
@ -118,3 +120,107 @@ sector_t* RenderViewpoint(FRenderViewpoint& mainvp, AActor* camera, IntRect* bou
|
|||
|
||||
return mainvp.sector;
|
||||
}
|
||||
|
||||
void DoWriteSavePic(FileWriter* file, ESSType ssformat, uint8_t* scr, int width, int height, sector_t* viewsector, bool upsidedown)
|
||||
{
|
||||
PalEntry palette[256];
|
||||
PalEntry modulateColor;
|
||||
auto blend = V_CalcBlend(viewsector, &modulateColor);
|
||||
int pixelsize = 1;
|
||||
// Apply the screen blend, because the renderer does not provide this.
|
||||
if (ssformat == SS_RGB)
|
||||
{
|
||||
int numbytes = width * height * 3;
|
||||
pixelsize = 3;
|
||||
if (modulateColor != 0xffffffff)
|
||||
{
|
||||
float r = modulateColor.r / 255.f;
|
||||
float g = modulateColor.g / 255.f;
|
||||
float b = modulateColor.b / 255.f;
|
||||
for (int i = 0; i < numbytes; i += 3)
|
||||
{
|
||||
scr[i] = uint8_t(scr[i] * r);
|
||||
scr[i + 1] = uint8_t(scr[i + 1] * g);
|
||||
scr[i + 2] = uint8_t(scr[i + 2] * b);
|
||||
}
|
||||
}
|
||||
float iblendfac = 1.f - blend.W;
|
||||
blend.X *= blend.W;
|
||||
blend.Y *= blend.W;
|
||||
blend.Z *= blend.W;
|
||||
for (int i = 0; i < numbytes; i += 3)
|
||||
{
|
||||
scr[i] = uint8_t(scr[i] * iblendfac + blend.X);
|
||||
scr[i + 1] = uint8_t(scr[i + 1] * iblendfac + blend.Y);
|
||||
scr[i + 2] = uint8_t(scr[i + 2] * iblendfac + blend.Z);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply the screen blend to the palette. The colormap related parts get skipped here because these are already part of the image.
|
||||
DoBlending(GPalette.BaseColors, palette, 256, uint8_t(blend.X), uint8_t(blend.Y), uint8_t(blend.Z), uint8_t(blend.W * 255));
|
||||
}
|
||||
|
||||
int pitch = width * pixelsize;
|
||||
if (upsidedown)
|
||||
{
|
||||
scr += ((height - 1) * width * pixelsize);
|
||||
pitch *= -1;
|
||||
}
|
||||
|
||||
M_CreatePNG(file, scr, ssformat == SS_PAL ? palette : nullptr, ssformat, width, height, pitch, Gamma);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// Render the view to a savegame picture
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void WriteSavePic(player_t* player, FileWriter* file, int width, int height)
|
||||
{
|
||||
if (!V_IsHardwareRenderer())
|
||||
{
|
||||
SWRenderer->WriteSavePic(player, file, width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
IntRect bounds;
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.width = width;
|
||||
bounds.height = height;
|
||||
auto& RenderState = *screen->RenderState();
|
||||
|
||||
// we must be sure the GPU finished reading from the buffer before we fill it with new data.
|
||||
screen->WaitForCommands(false);
|
||||
|
||||
// Switch to render buffers dimensioned for the savepic
|
||||
screen->SetSaveBuffers(true);
|
||||
screen->ImageTransitionScene(true);
|
||||
|
||||
hw_ClearFakeFlat();
|
||||
RenderState.SetVertexBuffer(screen->mVertexData);
|
||||
screen->mVertexData->Reset();
|
||||
screen->mLights->Clear();
|
||||
screen->mViewpoints->Clear();
|
||||
|
||||
// This shouldn't overwrite the global viewpoint even for a short time.
|
||||
FRenderViewpoint savevp;
|
||||
sector_t* viewsector = RenderViewpoint(savevp, players[consoleplayer].camera, &bounds, r_viewpoint.FieldOfView.Degrees, 1.6f, 1.6f, true, false);
|
||||
RenderState.EnableStencil(false);
|
||||
RenderState.SetNoSoftLightLevel();
|
||||
|
||||
int numpixels = width * height;
|
||||
uint8_t* scr = (uint8_t*)M_Malloc(numpixels * 3);
|
||||
screen->CopyScreenToBuffer(width, height, scr);
|
||||
|
||||
DoWriteSavePic(file, SS_RGB, scr, width, height, viewsector, screen->FlipSavePic());
|
||||
M_Free(scr);
|
||||
|
||||
// Switch back the screen render buffers
|
||||
screen->SetViewportRects(nullptr);
|
||||
screen->SetSaveBuffers(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,8 +57,6 @@ EXTERN_CVAR(Bool, r_drawvoxels)
|
|||
EXTERN_CVAR(Int, gl_tonemap)
|
||||
EXTERN_CVAR(Bool, cl_capfps)
|
||||
|
||||
void DoWriteSavePic(FileWriter* file, ESSType ssformat, uint8_t* scr, int width, int height, sector_t* viewsector, bool upsidedown);
|
||||
|
||||
extern bool NoInterpolateView;
|
||||
|
||||
void gl_LoadExtensions();
|
||||
|
@ -192,59 +190,18 @@ void OpenGLFrameBuffer::Update()
|
|||
Super::Update();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// Render the view to a savegame picture
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void OpenGLFrameBuffer::WriteSavePic(player_t *player, FileWriter *file, int width, int height)
|
||||
void OpenGLFrameBuffer::CopyScreenToBuffer(int width, int height, uint8_t* scr)
|
||||
{
|
||||
if (!V_IsHardwareRenderer())
|
||||
{
|
||||
Super::WriteSavePic(player, file, width, height);
|
||||
}
|
||||
else if (GLRenderer != nullptr)
|
||||
{
|
||||
IntRect bounds;
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.width = width;
|
||||
bounds.height = height;
|
||||
|
||||
// we must be sure the GPU finished reading from the buffer before we fill it with new data.
|
||||
glFinish();
|
||||
|
||||
// Switch to render buffers dimensioned for the savepic
|
||||
GLRenderer->mBuffers = GLRenderer->mSaveBuffers;
|
||||
|
||||
hw_ClearFakeFlat();
|
||||
gl_RenderState.SetVertexBuffer(screen->mVertexData);
|
||||
screen->mVertexData->Reset();
|
||||
screen->mLights->Clear();
|
||||
screen->mViewpoints->Clear();
|
||||
|
||||
// This shouldn't overwrite the global viewpoint even for a short time.
|
||||
FRenderViewpoint savevp;
|
||||
sector_t* viewsector = RenderViewpoint(savevp, players[consoleplayer].camera, &bounds, r_viewpoint.FieldOfView.Degrees, 1.6f, 1.6f, true, false);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
gl_RenderState.SetNoSoftLightLevel();
|
||||
GLRenderer->CopyToBackbuffer(&bounds, false);
|
||||
|
||||
// strictly speaking not needed as the glReadPixels should block until the scene is rendered, but this is to safeguard against shitty drivers
|
||||
glFinish();
|
||||
|
||||
int numpixels = width * height;
|
||||
uint8_t* scr = (uint8_t*)M_Malloc(numpixels * 3);
|
||||
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, scr);
|
||||
|
||||
DoWriteSavePic(file, SS_RGB, scr, width, height, viewsector, true);
|
||||
M_Free(scr);
|
||||
|
||||
// Switch back the screen render buffers
|
||||
screen->SetViewportRects(nullptr);
|
||||
GLRenderer->mBuffers = GLRenderer->mScreenBuffers;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -549,6 +506,18 @@ void OpenGLFrameBuffer::UpdateShadowMap()
|
|||
GLRenderer->UpdateShadowMap();
|
||||
}
|
||||
|
||||
void OpenGLFrameBuffer::WaitForCommands(bool finish)
|
||||
{
|
||||
glFinish();
|
||||
}
|
||||
|
||||
void OpenGLFrameBuffer::SetSaveBuffers(bool yes)
|
||||
{
|
||||
if (!GLRenderer) return;
|
||||
if (yes) GLRenderer->mBuffers = GLRenderer->mSaveBuffers;
|
||||
else GLRenderer->mBuffers = GLRenderer->mScreenBuffers;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
|
|
|
@ -32,13 +32,16 @@ public:
|
|||
void NextEye(int eyecount) override;
|
||||
void SetSceneRenderTarget(bool useSSAO) override;
|
||||
void UpdateShadowMap() override;
|
||||
void WaitForCommands(bool finish) override;
|
||||
void SetSaveBuffers(bool yes) override;
|
||||
void CopyScreenToBuffer(int width, int height, uint8_t* buffer) override;
|
||||
bool FlipSavePic() const override { return true; }
|
||||
|
||||
FRenderState* RenderState() override;
|
||||
void CleanForRestart() override;
|
||||
void UpdatePalette() override;
|
||||
uint32_t GetCaps() override;
|
||||
const char* DeviceName() const override;
|
||||
void WriteSavePic(player_t *player, FileWriter *file, int width, int height) override;
|
||||
sector_t *RenderView(player_t *player) override;
|
||||
void SetTextureFilterMode() override;
|
||||
IHardwareTexture *CreateHardwareTexture() override;
|
||||
|
|
|
@ -330,3 +330,4 @@ public:
|
|||
};
|
||||
|
||||
sector_t* RenderViewpoint(FRenderViewpoint& mainvp, AActor* camera, IntRect* bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen);
|
||||
void WriteSavePic(player_t* player, FileWriter* file, int width, int height);
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
#include "engineerrors.h"
|
||||
|
||||
void Draw2D(F2DDrawer *drawer, FRenderState &state);
|
||||
void DoWriteSavePic(FileWriter *file, ESSType ssformat, uint8_t *scr, int width, int height, sector_t *viewsector, bool upsidedown);
|
||||
|
||||
EXTERN_CVAR(Bool, r_drawvoxels)
|
||||
EXTERN_CVAR(Int, gl_tonemap)
|
||||
|
@ -218,17 +217,6 @@ void PolyFrameBuffer::Update()
|
|||
}
|
||||
|
||||
|
||||
void PolyFrameBuffer::WriteSavePic(player_t *player, FileWriter *file, int width, int height)
|
||||
{
|
||||
if (!V_IsHardwareRenderer())
|
||||
{
|
||||
Super::WriteSavePic(player, file, width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
sector_t *PolyFrameBuffer::RenderView(player_t *player)
|
||||
{
|
||||
// To do: this is virtually identical to FGLRenderer::RenderView and should be merged.
|
||||
|
|
|
@ -40,7 +40,6 @@ public:
|
|||
void PrecacheMaterial(FMaterial *mat, int translation) override;
|
||||
void UpdatePalette() override;
|
||||
uint32_t GetCaps() override;
|
||||
void WriteSavePic(player_t *player, FileWriter *file, int width, int height) override;
|
||||
sector_t *RenderView(player_t *player) override;
|
||||
void SetTextureFilterMode() override;
|
||||
void TextureFilterChanged() override;
|
||||
|
|
|
@ -312,12 +312,6 @@ uint32_t DFrameBuffer::GetCaps()
|
|||
return (uint32_t)FlagSet;
|
||||
}
|
||||
|
||||
void DFrameBuffer::WriteSavePic(player_t *player, FileWriter *file, int width, int height)
|
||||
{
|
||||
SWRenderer->WriteSavePic(player, file, width, height);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Calculates the viewport values needed for 2D and 3D operations
|
||||
|
|
|
@ -309,13 +309,18 @@ public:
|
|||
virtual uint32_t GetCaps();
|
||||
virtual int Backend() { return 0; }
|
||||
virtual const char* DeviceName() const { return "Unknown"; }
|
||||
virtual void WriteSavePic(player_t *player, FileWriter *file, int width, int height);
|
||||
virtual sector_t *RenderView(player_t *player) { return nullptr; }
|
||||
virtual void AmbientOccludeScene(float m5) {}
|
||||
virtual void FirstEye() {}
|
||||
virtual void NextEye(int eyecount) {}
|
||||
virtual void SetSceneRenderTarget(bool useSSAO) {}
|
||||
virtual void UpdateShadowMap() {}
|
||||
virtual void WaitForCommands(bool finish) {}
|
||||
virtual void SetSaveBuffers(bool yes) {}
|
||||
virtual void ImageTransitionScene(bool unknown) {}
|
||||
virtual void CopyScreenToBuffer(int width, int height, uint8_t* buffer) { memset(buffer, 0, width* height); }
|
||||
virtual bool FlipSavePic() const { return false; }
|
||||
|
||||
|
||||
// Screen wiping
|
||||
virtual FTexture *WipeStartScreen();
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
#include "engineerrors.h"
|
||||
|
||||
void Draw2D(F2DDrawer *drawer, FRenderState &state);
|
||||
void DoWriteSavePic(FileWriter *file, ESSType ssformat, uint8_t *scr, int width, int height, sector_t *viewsector, bool upsidedown);
|
||||
|
||||
EXTERN_CVAR(Bool, r_drawvoxels)
|
||||
EXTERN_CVAR(Int, gl_tonemap)
|
||||
|
@ -318,53 +317,6 @@ void VulkanFrameBuffer::WaitForCommands(bool finish)
|
|||
}
|
||||
}
|
||||
|
||||
void VulkanFrameBuffer::WriteSavePic(player_t *player, FileWriter *file, int width, int height)
|
||||
{
|
||||
if (!V_IsHardwareRenderer())
|
||||
{
|
||||
Super::WriteSavePic(player, file, width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
IntRect bounds;
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.width = width;
|
||||
bounds.height = height;
|
||||
|
||||
// we must be sure the GPU finished reading from the buffer before we fill it with new data.
|
||||
WaitForCommands(false);
|
||||
|
||||
// Switch to render buffers dimensioned for the savepic
|
||||
mActiveRenderBuffers = mSaveBuffers.get();
|
||||
|
||||
mPostprocess->ImageTransitionScene(true);
|
||||
|
||||
hw_ClearFakeFlat();
|
||||
GetRenderState()->SetVertexBuffer(screen->mVertexData);
|
||||
screen->mVertexData->Reset();
|
||||
screen->mLights->Clear();
|
||||
screen->mViewpoints->Clear();
|
||||
|
||||
// This shouldn't overwrite the global viewpoint even for a short time.
|
||||
FRenderViewpoint savevp;
|
||||
sector_t *viewsector = RenderViewpoint(savevp, players[consoleplayer].camera, &bounds, r_viewpoint.FieldOfView.Degrees, 1.6f, 1.6f, true, false);
|
||||
GetRenderState()->EnableStencil(false);
|
||||
GetRenderState()->SetNoSoftLightLevel();
|
||||
|
||||
int numpixels = width * height;
|
||||
uint8_t * scr = (uint8_t *)M_Malloc(numpixels * 3);
|
||||
CopyScreenToBuffer(width, height, scr);
|
||||
|
||||
DoWriteSavePic(file, SS_RGB, scr, width, height, viewsector, false);
|
||||
M_Free(scr);
|
||||
|
||||
// Switch back the screen render buffers
|
||||
screen->SetViewportRects(nullptr);
|
||||
mActiveRenderBuffers = mScreenBuffers.get();
|
||||
}
|
||||
}
|
||||
|
||||
sector_t *VulkanFrameBuffer::RenderView(player_t *player)
|
||||
{
|
||||
// To do: this is virtually identical to FGLRenderer::RenderView and should be merged.
|
||||
|
@ -634,7 +586,7 @@ FTexture *VulkanFrameBuffer::WipeEndScreen()
|
|||
return tex;
|
||||
}
|
||||
|
||||
void VulkanFrameBuffer::CopyScreenToBuffer(int w, int h, void *data)
|
||||
void VulkanFrameBuffer::CopyScreenToBuffer(int w, int h, uint8_t *data)
|
||||
{
|
||||
VkTextureImage image;
|
||||
|
||||
|
@ -866,6 +818,18 @@ void VulkanFrameBuffer::UpdateShadowMap()
|
|||
mPostprocess->UpdateShadowMap();
|
||||
}
|
||||
|
||||
void VulkanFrameBuffer::SetSaveBuffers(bool yes)
|
||||
{
|
||||
if (yes) mActiveRenderBuffers = mSaveBuffers.get();
|
||||
else mActiveRenderBuffers = mScreenBuffers.get();
|
||||
}
|
||||
|
||||
void VulkanFrameBuffer::ImageTransitionScene(bool unknown)
|
||||
{
|
||||
mPostprocess->ImageTransitionScene(unknown);
|
||||
}
|
||||
|
||||
|
||||
FRenderState* VulkanFrameBuffer::RenderState()
|
||||
{
|
||||
return mRenderState.get();
|
||||
|
|
|
@ -80,7 +80,6 @@ public:
|
|||
uint32_t GetCaps() override;
|
||||
const char* DeviceName() const override;
|
||||
int Backend() override { return 1; }
|
||||
void WriteSavePic(player_t *player, FileWriter *file, int width, int height) override;
|
||||
sector_t *RenderView(player_t *player) override;
|
||||
void SetTextureFilterMode() override;
|
||||
void TextureFilterChanged() override;
|
||||
|
@ -91,6 +90,8 @@ public:
|
|||
void AmbientOccludeScene(float m5) override;
|
||||
void SetSceneRenderTarget(bool useSSAO) override;
|
||||
void UpdateShadowMap() override;
|
||||
void SetSaveBuffers(bool yes) override;
|
||||
void ImageTransitionScene(bool unknown) override;
|
||||
|
||||
IHardwareTexture *CreateHardwareTexture() override;
|
||||
FMaterial* CreateMaterial(FGameTexture* tex, int scaleflags) override;
|
||||
|
@ -108,7 +109,7 @@ public:
|
|||
|
||||
void Draw2D() override;
|
||||
|
||||
void WaitForCommands(bool finish);
|
||||
void WaitForCommands(bool finish) override;
|
||||
|
||||
void PushGroup(const FString &name);
|
||||
void PopGroup();
|
||||
|
@ -118,7 +119,7 @@ private:
|
|||
void RenderTextureView(FCanvasTexture *tex, AActor *Viewpoint, double FOV);
|
||||
void PrintStartupLog();
|
||||
void CreateFanToTrisIndexBuffer();
|
||||
void CopyScreenToBuffer(int w, int h, void *data);
|
||||
void CopyScreenToBuffer(int w, int h, uint8_t *data) override;
|
||||
void DeleteFrameObjects();
|
||||
void FlushCommands(VulkanCommandBuffer **commands, size_t count, bool finish, bool lastsubmit);
|
||||
|
||||
|
|
Loading…
Reference in a new issue