NightQuest Posted February 26, 2015 Share Posted February 26, 2015 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? Quote Link to comment Share on other sites More sharing options...
Josh Posted February 26, 2015 Share Posted February 26, 2015 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! 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
NightQuest Posted February 26, 2015 Author Share Posted February 26, 2015 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. 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. 1 Quote Link to comment Share on other sites More sharing options...
NightQuest Posted February 27, 2015 Author Share Posted February 27, 2015 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; } Quote Link to comment Share on other sites More sharing options...
Guppy Posted February 27, 2015 Share Posted February 27, 2015 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 ? Quote System: Linux Mint 17 ( = Ubuntu 14.04 with cinnamon desktop ) Ubuntu 14.04, AMD HD 6850, i5 2500k Link to comment Share on other sites More sharing options...
NightQuest Posted February 27, 2015 Author Share Posted February 27, 2015 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. Quote Link to comment Share on other sites More sharing options...
Guppy Posted February 27, 2015 Share Posted February 27, 2015 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. Quote System: Linux Mint 17 ( = Ubuntu 14.04 with cinnamon desktop ) Ubuntu 14.04, AMD HD 6850, i5 2500k Link to comment Share on other sites More sharing options...
AnthonyPython Posted February 27, 2015 Share Posted February 27, 2015 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. 1 Quote OS: Windows 10 Pro CPU: i3-10100 CPU @ 3.60GHz GPU: NVIDIA 2060 Super - 8 GB RAM: 32 GB Link to comment Share on other sites More sharing options...
Gloomshroud Posted February 28, 2015 Share Posted February 28, 2015 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. 1 Quote Link to comment Share on other sites More sharing options...
AnthonyPython Posted February 28, 2015 Share Posted February 28, 2015 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) 1 Quote OS: Windows 10 Pro CPU: i3-10100 CPU @ 3.60GHz GPU: NVIDIA 2060 Super - 8 GB RAM: 32 GB Link to comment Share on other sites More sharing options...
NightQuest Posted March 1, 2015 Author Share Posted March 1, 2015 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; } Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.