Jump to content
NightQuest

UHD+ Screenshots

Recommended Posts

One of my favorite features in some newer titles (I'm looking at you, Elite Dangerous) has been the ability to render screenshots that are well beyond the users screen resolution.

 

The built in Context::Screenshot does not support this, so I made one that did.

 

Sadly, I can only seem to squeeze 8K out of it, which takes roughly ~10 seconds to complete.

 

I figured the fix for this would be to take a series of screenshots, and stitch them together prior to saving.

 

I am however, completely lost. I think I'd have to move the camera and zoom in, and yet somehow preserve parallax.

 

Any tips on how to proceed?

 

#pragma pack(push, 1)
struct TGAHeader
{
   BYTE IDLength;
   BYTE ColormapType;
   BYTE ImageType;
   WORD ColorMapStart;
   WORD ColorMapLength;
   BYTE ColorMapDepth;
   WORD XOrigin;
   WORD YOrigin;
   WORD ImageWidth;
   WORD ImageHeight;
   BYTE PixelDepth;
   BYTE ImageDescriptor;
};
#pragma pack(pop)

bool App::ScreenshotHighRes(const std::string& filename, const unsigned int scale)
{
   Buffer* current = Buffer::GetCurrent();
   float par = (float)current->GetWidth() / (float)current->GetHeight();
   return ScreenshotHighRes(filename, (current->GetHeight() * scale) * par, current->GetHeight() * scale);
}

bool App::ScreenshotHighRes(const std::string& filename, const unsigned int width, const unsigned int height)
{
   bool ret = false;

   // Set our new buffer and render
   Buffer* oldbuf = Buffer::GetCurrent();
   Buffer* buffer = Buffer::Create(width, height);
   buffer->SetColorTexture(Texture::Create(width, height));
   Buffer::SetCurrent(buffer);
   World::GetCurrent()->Render();

   // Capture the texture
   Texture* tex = buffer->GetColorTexture();
   GLuint texSize = tex->GetMipmapSize(0);
   GLuint texWidth = tex->GetWidth();
   GLuint texHeight = tex->GetHeight();
   GLubyte* pixels = new GLubyte[texSize];
   memset(pixels, 0, texSize);
   tex->GetPixels(reinterpret_cast<const char*>(pixels));

   // Minor cleanup
   Buffer::SetCurrent(oldbuf);
   tex->Release();
   buffer->Release();

   // BGRA -> RGB
   GLubyte* tmp = new GLubyte[(texWidth * 3) * texHeight];
   for( GLuint y = 0, x = 0; x < texSize; x += 4, y += 3 )
   {
       tmp[y] = pixels[x + 2];
       tmp[y + 1] = pixels[x + 1];
       tmp[y + 2] = pixels[x];
   }
   delete[] pixels;
   pixels = tmp;

   // Flip image vertically
   GLuint rowLength = texWidth * 3;
   GLubyte* line = new GLubyte[rowLength];
   for( GLuint row = 0; row < texHeight / 2; row++ )
   {
       memcpy(line, pixels + (row * rowLength), rowLength);
       memcpy(pixels + (row * rowLength), pixels + ((texHeight - row - 1) * rowLength), rowLength);
       memcpy(pixels + ((texHeight - row - 1) * rowLength), line, rowLength);
   }
   delete[] line;

   // Write TGA
   TGAHeader tgah = { 0 };
   tgah.ImageType = 2;
   tgah.ImageWidth = texWidth;
   tgah.ImageHeight = texHeight;
   tgah.PixelDepth = 24;
   Stream* file = FileSystem::WriteFile(filename);
   if( file )
   {
       file->Write(&tgah, sizeof(TGAHeader));
       file->Write(pixels, (texWidth * 3) * texHeight);
       file->Release();
       ret = true;
   }

   delete[] pixels;

   return ret;
}

 

EDIT:

It looks like I'll need to modify the Cameras projection matrix for this - is that exposed?

Share this post


Link to post

You could send a matrix to the shader that makes the camera render only a part of its view...but that would be tricky and I am not 100% sure it would work.

 

8k is pretty big man!

Share this post


Link to post

8k is pretty big man!

It is, but my aim was to low-end proof it - by stitching it together like that, someone with a low-end machine would still be able to get a 8K screen if they wanted to. smile.png

 

You could send a matrix to the shader that makes the camera render only a part of its view...but that would be tricky and I am not 100% sure it would work.

That's essentially what I was going for - taking the view and chopping it up into, say 3x3 screenshots - then using a smiliar method as above to move them all into a single image.

 

I may have to just settle for this, as it works pretty good - it's just limiting on some machines.

Share this post


Link to post

From what I can tell, this should work (and is far faster - it happens within a second) - but it looks like World::Render() might be interfering with the matrix being set - is there a way to force it to use mine?

 

bool ScreenshotHighRes(const std::string& filename, GLint scale)
{
   if( scale <= 1 )
       return Context::GetCurrent()->Screenshot(filename);

   bool ret = false;

   GLint    tileWidth = Context::GetCurrent()->GetWidth(),
           tileHeight = Context::GetCurrent()->GetHeight();
   GLint    finalWidth = tileWidth * scale,
           finalHeight = tileHeight * scale;

   Buffer* oBuffer = Buffer::GetCurrent();
   Buffer* buffer = Buffer::Create(tileWidth, tileHeight);
   Texture* tex = Texture::Create(tileWidth, tileHeight);
   buffer->SetColorTexture(tex);
   Buffer::SetCurrent(buffer);

   GLuint texSize = tex->GetMipmapSize(0);

   GLubyte* tPixels = new GLubyte[texSize];
   memset(tPixels, 0, texSize);

   GLubyte* pixels = new GLubyte[(tileWidth * 3) * tileHeight];
   memset(pixels, 0, (tileWidth * 3) * tileHeight);

   GLuint rowLength = tileWidth * 3;
   GLubyte* line = new GLubyte[rowLength];
   memset(line, 0, rowLength);


   GLfloat pm[16];
   glGetFloatv(GL_PROJECTION_MATRIX, &pm[0]);

   glMatrixMode(GL_PROJECTION);
   glPushMatrix();

   for( GLint y = 0; y < scale; ++y )
   {
       for( GLint x = 0; x < scale; ++x )
       {
           buffer->Clear();

           // Zoom in to the tile
           glLoadIdentity();
           glTranslatef(tileWidth - 2.0*x - 1, tileHeight - 2.0*y - 1, 0.0);
           glScalef(tileWidth, tileHeight, 1);
           glMultMatrixf(&pm[0]);

           World::GetCurrent()->Render();

           tex->GetPixels(reinterpret_cast<const char*>(tPixels));

           // BGRA -> RGB
           for( GLuint p = 0, t = 0; t < texSize; t += 4, p += 3 )
           {
               pixels[p] = tPixels[t + 2];
               pixels[p + 1] = tPixels[t + 1];
               pixels[p + 2] = tPixels[t];
           }

           // Flip image vertically
           for( GLint row = 0; row < tileHeight / 2; row++ )
           {
               memcpy(line, pixels + (row * rowLength), rowLength);
               memcpy(pixels + (row * rowLength), pixels + ((tileHeight - row - 1) * rowLength), rowLength);
               memcpy(pixels + ((tileHeight - row - 1) * rowLength), line, rowLength);
           }

           // Write TGA
           TGAHeader tgah = { 0 };
           tgah.ImageType = 2;
           tgah.ImageWidth = tileWidth;
           tgah.ImageHeight = tileHeight;
           tgah.PixelDepth = 24;
           Stream* file = FileSystem::WriteFile(filename + "_" + to_string(x) + "_" + to_string(y) + ".tga");
           if( file )
           {
               file->Write(&tgah, sizeof(TGAHeader));
               file->Write(pixels, (tileWidth * 3) * tileHeight);
               file->Release();
           }
       }
   }
   glPopMatrix();


   delete[] tPixels;
   delete[] pixels;
   delete[] line;

   Buffer::SetCurrent(oBuffer);
   buffer->GetColorTexture()->Release();
   buffer->Release();

   return ret;
}

Share this post


Link to post

I don't see why your messing with camera projection and stitching ( unless you want to make panoramas ofcourse )

 

Have a look here;

http://www.43rumors.com/ft5-e-m5-successor-has-sensor-shift-to-create-up-to-40-megapixel-images-on-the-fly/

 

Grabbing 9 screen shots shifted like in the above link should take 0.15s with a bit of extra time for post processing

 

But maybe I'm missing something ?

Share this post


Link to post

I don't see why your messing with camera projection and stitching ( unless you want to make panoramas ofcourse )

 

Have a look here;

http://www.43rumors.com/ft5-e-m5-successor-has-sensor-shift-to-create-up-to-40-megapixel-images-on-the-fly/

 

Grabbing 9 screen shots shifted like in the above link should take 0.15s with a bit of extra time for post processing

 

But maybe I'm missing something ?

 

That's actually exactly what I'm trying to do. This is a pretty foreign thing to me, so I'm sure I'm on the wrong track.. But from what I can tell I need to adjust the viewport while preserving the camera's location and angle as to not mess with parallax.

 

Before this, I had tried to use gluPickMatrix but that didn't produce any effect at all; I assumed this was because it's deprecated so I gave this a shot.. Which also seemingly did nothing.. :/

 

Sorry for typos, wrote this on my phone.

Share this post


Link to post

if your moving the camera just 1 px I'd not worry about parallax - more likely rounding errors may mean you end up with 9 identical images.

 

Another solution could be to move the camera closer and do X seperate frames that each fit inside the original view cone - if that makes sense.

Share this post


Link to post

if you get the stitching that 8K version finished, I'll be more than glad to test it on my low end hardware. and if it works I would like this to be a extra option for my project.

Share this post


Link to post

This entire post and subsequent thread might as well have been in Koine Greek...but this is EXACTLY why I'm getting involved with Leadwerks. I want to peek under the hood and see what's going on in an engine. Especially when it's being tinkered with and pushed to the limit. I am not a stupid person, but this whole thing made my eyes cross.

Share this post


Link to post

This entire post and subsequent thread might as well have been in Koine Greek...but this is EXACTLY why I'm getting involved with Leadwerks. I want to peek under the hood and see what's going on in an engine. Especially when it's being tinkered with and pushed to the limit. I am not a stupid person, but this whole thing made my eyes cross.

 

I am not the best by any means with C++, but I do like tinkering with C++ in engines and stuff, I tinkered around with the 2013 source engine code before migrating to leadwerks (sorry valve)

Share this post


Link to post

I was able to get it to take the top left at the correct scale by passing the final size to buffer, but not the texture. However, It looks like World::Render() always renders at 0,0

 

This caused me to look at camera::setViewport(), but I cannot seem to use it correctly.

 

Has anyone used this before?

 

bool ScreenshotHighRes(Camera* camera, const std::string& filename, GLint scale)
{
   if( scale <= 1 )
       return Context::GetCurrent()->Screenshot(filename);

   bool ret = false;

   GLint    tileWidth = Context::GetCurrent()->GetWidth(),
           tileHeight = Context::GetCurrent()->GetHeight();
   GLint    finalWidth = tileWidth * scale,
           finalHeight = tileHeight * scale;

   Buffer* oBuffer = Buffer::GetCurrent();
   Buffer* buffer = Buffer::Create(finalWidth, finalHeight);
   Texture* tex = Texture::Create(tileWidth, tileHeight);
   buffer->SetColorTexture(tex);
   Buffer::SetCurrent(buffer);

   GLuint texSize = tex->GetMipmapSize(0);

   GLubyte* tPixels = new GLubyte[texSize];
   memset(tPixels, 0, texSize);

   GLubyte* pixels = new GLubyte[(tileWidth * 3) * tileHeight];
   memset(pixels, 0, (tileWidth * 3) * tileHeight);

   GLuint rowLength = tileWidth * 3;
   GLubyte* line = new GLubyte[rowLength];
   memset(line, 0, rowLength);

   GLuint count = 0;
   for( GLint y = 0; y < scale; ++y )
   {
       for( GLint x = 0; x < scale; ++x )
       {
           camera->SetViewport((x - 1)*tileWidth, (y - 1)*tileHeight, tileWidth, tileHeight);

           World::GetCurrent()->Render();

           tex->GetPixels(reinterpret_cast<const char*>(tPixels));

           // BGRA -> RGB
           for( GLuint p = 0, t = 0; t < texSize; t += 4, p += 3 )
           {
               pixels[p] = tPixels[t + 2];
               pixels[p + 1] = tPixels[t + 1];
               pixels[p + 2] = tPixels[t];
           }

           // Flip image vertically
           for( GLint row = 0; row < tileHeight / 2; row++ )
           {
               memcpy(line, pixels + (row * rowLength), rowLength);
               memcpy(pixels + (row * rowLength), pixels + ((tileHeight - row - 1) * rowLength), rowLength);
               memcpy(pixels + ((tileHeight - row - 1) * rowLength), line, rowLength);
           }

           // Write TGA
           TGAHeader tgah = { 0 };
           tgah.ImageType = 2;
           tgah.ImageWidth = tileWidth;
           tgah.ImageHeight = tileHeight;
           tgah.PixelDepth = 24;
           Stream* file = FileSystem::WriteFile(filename + "_" + to_string(x) + "_" + to_string(y) + ".tga");
           if( file )
           {
               file->Write(&tgah, sizeof(TGAHeader));
               file->Write(pixels, (tileWidth * 3) * tileHeight);
               file->Release();
               count++;
           }
       }
   }

   if( count == scale )
       ret = true;

   delete[] tPixels;
   delete[] pixels;
   delete[] line;

   Buffer::SetCurrent(oBuffer);
   tex->Release();
   buffer->Release();

   return ret;
}

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...