viewImageBlitOverride/viewImageBlitOverride.cpp

viewImageBlitOverride/viewImageBlitOverride.cpp
//-
// Copyright 2015 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk license agreement
// provided at the time of installation or download, or which otherwise
// accompanies this software in either electronic or hard copy form.
//+
#include <maya/M3dView.h>
#include "viewImageBlitOverride.h"
#include <maya/MDrawContext.h>
#include <maya/MFrameContext.h>
#include <maya/MImage.h>
#include <maya/MStateManager.h>
#include <maya/MGlobal.h>
#include <stdlib.h>
//
// Sample plugin which will blit an image as the scene and rely on
// Maya's internal rendering for the UI only
//
// Classes:
//
// RenderOverride: The main override class. Contains all the operations
// as well as keep track of texture resources.
// SceneBlit: A simple quad render responsible for blitting a
// colour and depth image. Will also clear the background depth
// UIDraw: A scene override which filters out all but UI drawing
//
// A stock "present" operation is also queued so that the results go to the viewport
//
namespace viewImageBlitOverride
{
// Global override instance
/*static*/ RenderOverride* RenderOverride::sViewImageBlitOverrideInstance = NULL;
// Override
//
RenderOverride::RenderOverride( const MString & name )
: MRenderOverride( name )
, mUIName("Sample Image Blit Override")
, mCurrentOperation(-1)
, mLoadImagesFromDisk(false)
{
mOperations[0] = mOperations[1] = mOperations[2] = mOperations[3] = NULL;
mOperationNames[0] = "viewImageBlitOverride_SceneBlit";
mOperationNames[1] = "viewImageBlitOverride_UIDraw";
mOperationNames[2] = "viewImageBlitOverride_Present";
mColorTexture.texture = NULL;
mColorTextureDesc.setToDefault2DTexture();
mDepthTexture.texture = NULL;
mDepthTextureDesc.setToDefault2DTexture();
}
RenderOverride::~RenderOverride()
{
// Release textures
MHWRender::MTextureManager* textureManager = renderer ? renderer->getTextureManager() : NULL;
if (textureManager)
{
if (mColorTexture.texture)
textureManager->releaseTexture(mColorTexture.texture);
if (mDepthTexture.texture)
textureManager->releaseTexture(mDepthTexture.texture);
}
// Release operations
for (unsigned int i=0; i<4; i++)
{
if (mOperations[i])
{
delete mOperations[i];
mOperations[i] = NULL;
}
}
}
MHWRender::DrawAPI RenderOverride::supportedDrawAPIs() const
{
}
// Basic iterator methods
bool RenderOverride::startOperationIterator()
{
mCurrentOperation = 0;
return true;
}
MHWRender::MRenderOperation *RenderOverride::renderOperation()
{
if (mCurrentOperation >= 0 && mCurrentOperation < 4)
{
if (mOperations[mCurrentOperation])
{
return mOperations[mCurrentOperation];
}
}
return NULL;
}
bool RenderOverride::nextRenderOperation()
{
mCurrentOperation++;
if (mCurrentOperation < 4)
{
return true;
}
return false;
}
//
// Update textures used for scene blit. Will update both a
// colour and a deph texture based on the current output target size.
//
bool RenderOverride::updateTextures(MHWRender::MRenderer *theRenderer,
MHWRender::MTextureManager* textureManager)
{
if (!theRenderer || !textureManager)
return false;
// Get current output size.
unsigned int targetWidth = 0;
unsigned int targetHeight = 0;
theRenderer->outputTargetSize( targetWidth, targetHeight );
bool acquireNewTexture = false;
// Decide whether to load images from disk
// If loading from disk, will use a single iff file which contains both
// color and depth. Seperate files for color and depth could be chosen.
//
// For this example, we just use an environment variable to switch
// to show both execution paths.
//
int val = 0;
bool forceReload = false;
if (MGlobal::getOptionVarValue("VIBO_LoadImagesFromDisk", val))
{
bool loadFromDisk = (val > 0);
if (loadFromDisk != mLoadImagesFromDisk)
{
mLoadImagesFromDisk = loadFromDisk;
forceReload = true;
}
}
MString imageLocation(MString(getenv("MAYA_LOCATION")) + MString("\\devkit\\plug-ins\\viewImageBlitOverride\\"));
MString colorImageFileName( imageLocation + MString("renderedImage.iff") );
MString depthImageFileName( colorImageFileName );
// If a resize occured, or we haven't allocated any texture yet,
// then create new textures which match the output size.
// Release any existing textures.
//
if (forceReload ||
!mColorTexture.texture ||
!mDepthTexture.texture ||
(!mLoadImagesFromDisk &&
(mColorTextureDesc.fWidth != targetWidth ||
mColorTextureDesc.fHeight != targetHeight)))
{
if (mColorTexture.texture)
{
textureManager->releaseTexture( mColorTexture.texture );
mColorTexture.texture = NULL;
}
if (mDepthTexture.texture)
{
textureManager->releaseTexture( mDepthTexture.texture );
mDepthTexture.texture = NULL;
}
acquireNewTexture = true;
forceReload = false;
}
if (!mColorTexture.texture)
{
unsigned char *textureData = NULL;
MImage image;
if (mLoadImagesFromDisk)
{
image.readFromFile( colorImageFileName );
image.getSize( targetWidth, targetHeight );
textureData = image.pixels();
}
else
{
// Prepare some data which forms a checker pattern
//
textureData = new unsigned char[4*targetWidth*targetHeight];
if (textureData)
{
for (unsigned int y = 0; y < targetHeight; y++)
{
unsigned char* pPixel = textureData + (y * targetWidth * 4);
for (unsigned int x = 0; x < targetWidth; x++)
{
bool checker = (((x >> 5) & 1) ^ ((y >> 5) & 1)) != 0;
*pPixel++ = checker ? 255 : 0;
*pPixel++ = 0;
*pPixel++ = 0;
*pPixel++ = 255;
}
}
}
}
mColorTextureDesc.fWidth = targetWidth;
mColorTextureDesc.fHeight = targetHeight;
mColorTextureDesc.fDepth = 1;
mColorTextureDesc.fBytesPerRow = 4*targetWidth;
mColorTextureDesc.fBytesPerSlice = mColorTextureDesc.fBytesPerRow*targetHeight;
// Acquire a new texture.
mColorTexture.texture = textureManager->acquireTexture( "", mColorTextureDesc, textureData );
if (mColorTexture.texture)
mColorTexture.texture->textureDescription( mColorTextureDesc );
if (!mLoadImagesFromDisk)
{
// Don't need the data any more after upload to texture
delete [] textureData;
}
}
// Various methods for updating the colour texture with some sample data
// for a checker pattern.
//
// 1. The raw data can be read back from an existing texture, modified and sent
// back to update the texture. This involves a GPU->CPU read and a CPU->GPU
// transfer. Toggled using the 'updateWithRawData' variable.
// 2. Raw data is allocated by the plug-in and a CPU-GPU transfer is used.
// This is the same update interface as used in method 1.
// 3. Data is allocated in an MImage and data is tranferred to the texture
// in a CPU->GPU transfer. Toggle using the 'updateWithMImage' variable.
//
// All three are shown, with suitability to circumstances
// left to the plug-in writer to choose. The code as is follow option 2.
//
else
{
// Test code to update the image on every refresh
//
// The update code assumes the image size is the viewport size, so
// don't use if loading from disk as the disk image may not be the viewport size.
bool updateOnEveryRefresh = false; // !mLoadImagesFromDisk;
bool generateMipMaps = false;
if (updateOnEveryRefresh)
{
// Test to read back data from the texture and update on
// the copy. Does a colour invert.
bool updateWithRawData = false;
if (updateWithRawData)
{
mColorTexture.texture->textureDescription( mColorTextureDesc );
int rowPitch = 0;
int slicePitch = 0;
unsigned char* textureData = (unsigned char *)mColorTexture.texture->rawData(rowPitch, slicePitch);
unsigned char* val = NULL;
if (textureData && rowPitch > 0 && slicePitch > 0)
{
for (unsigned int i=0; i<mColorTextureDesc.fHeight; i++)
{
val = textureData + (i*rowPitch);
for (unsigned int j=0; j<mColorTextureDesc.fWidth*4; j++)
{
*val = 255 - *val;
val++;
}
}
// Call the update method with raw data
mColorTexture.texture->update(textureData, generateMipMaps, rowPitch);
}
delete [] textureData;
}
// Test to send over new data to update the texture
else
{
MImage image;
unsigned char* textureData = NULL;
bool updateWithMImage = true;
unsigned int tile = 5;
// MImage allocation
if (updateWithMImage)
{
image.create(targetWidth, targetHeight, 4, MImage::kByte);
textureData = image.pixels();
}
// Raw data allocation
else
{
textureData = new unsigned char[4*targetWidth*targetHeight];
}
// Some sample code to rotate colouring the checker differently
// per update.
if (textureData)
{
static unsigned int counter = 0;
for (unsigned int y = 0; y < targetHeight; y++)
{
unsigned char* pPixel = textureData + (y * targetWidth * 4);
for (unsigned int x = 0; x < targetWidth; x++)
{
bool checker = (((x >> tile) & 1) ^ ((y >> tile) & 1)) != 0;
*pPixel++ = (counter == 0 && checker) ? 255 : 0;
*pPixel++ = (counter == 1 && checker) ? 255 : 0;
*pPixel++ = (counter == 2 && checker) ? 255 : 0;
*pPixel++ = 255;
}
}
counter = (counter + 1) % 3;
}
// Call the update method with raw data
if (!updateWithMImage)
{
mColorTexture.texture->update( textureData, generateMipMaps );
delete [] textureData;
}
// Call the update method with an MImage
else
{
mColorTexture.texture->update( image, generateMipMaps );
}
}
}
}
// Acquire a new "depth" texture as necessary
//
if (!mDepthTexture.texture)
{
MImage image;
image.create(targetWidth, targetHeight, 4, MImage::kByte);
mDepthTextureDesc.fWidth = targetWidth;
mDepthTextureDesc.fHeight = targetHeight;
mDepthTextureDesc.fDepth = 1;
mDepthTextureDesc.fBytesPerRow = targetWidth;
mDepthTextureDesc.fBytesPerSlice = mDepthTextureDesc.fBytesPerRow*targetHeight;
// Load depth image from disk to create depth texture. Uses the MImage texture creation interface.
//
if (mLoadImagesFromDisk)
{
image.readDepthMap( depthImageFileName );
image.getDepthMapSize( targetWidth, targetHeight );
mDepthTexture.texture = textureManager->acquireDepthTexture("",
image, false, &normalizationDesc );
}
// Load depth using programmatically created data
//
else
{
float *textureData = new float[targetWidth*targetHeight];
if (textureData)
{
// Use 'createDepthWithMImage' to switch between using the MImage
// and raw data interfaces.
//
// Use 'useCameraDistanceValues' to switch between using -1/distance-to-camera
// values versus using normalized depth coordinates [0...1].
// The flag is set to create normalized values by default in order to
// match the requirements of the shader used to render the texture.
// Refer to the the comments in SceneBlit::shader() for more details.
//
bool createDepthWithMImage = false;
bool useCameraDistanceValues = false;
// Create some dummy 'checkered' depth data.
//
float depthValue = useCameraDistanceValues ? -1.0f / 100.0f : 1.0f;
float depthValue2 = useCameraDistanceValues ? -1.0f / 500.0f : 0.98f;
for (unsigned int y = 0; y < targetHeight; y++)
{
float* pPixel = textureData + (y * targetWidth);
for (unsigned int x = 0; x < targetWidth; x++)
{
bool checker = (((x >> 5) & 1) ^ ((y >> 5) & 1)) != 0;
*pPixel++ = checker ? depthValue : depthValue2;
}
}
if (createDepthWithMImage)
{
image.setDepthMap( textureData, targetWidth, targetHeight );
mDepthTexture.texture = textureManager->acquireDepthTexture("",
image, false, useCameraDistanceValues ? &normalizationDesc : NULL );
}
else
{
mDepthTexture.texture = textureManager->acquireDepthTexture("",
textureData, targetWidth, targetHeight, false,
useCameraDistanceValues ? &normalizationDesc : NULL );
}
// Not required anymore so can delete it.
delete [] textureData;
}
}
if (mDepthTexture.texture)
mDepthTexture.texture->textureDescription( mDepthTextureDesc );
}
// Update the textures used for the blit operation.
//
if (acquireNewTexture)
{
SceneBlit* blit = (SceneBlit *)mOperations[0];
blit->setColorTexture( mColorTexture );
blit->setDepthTexture( mDepthTexture );
}
return (mDepthTexture.texture && mColorTexture.texture);
}
//
// Create the set of operations as necessary, update
// textures for image blits, and install a callback so we can setup and
// cleanup appropriatelyt when the override changes.
//
MStatus RenderOverride::setup( const MString & destination )
{
if (!theRenderer)
MHWRender::MTextureManager* textureManager = theRenderer->getTextureManager();
if (!textureManager)
// Create a new set of operations as required
if (!mOperations[0])
{
mOperations[0] = (MHWRender::MRenderOperation *) new SceneBlit( mOperationNames[0] );
mOperations[1] = (MHWRender::MRenderOperation *) new UIDraw( mOperationNames[1] );
mOperations[3] = (MHWRender::MRenderOperation *) new MHWRender::MPresentTarget( mOperationNames[2] );
}
if (!mOperations[0] ||
!mOperations[1] ||
!mOperations[2] ||
!mOperations[3])
{
}
// Update textures used for scene blit
if (!updateTextures(theRenderer, textureManager))
//
// Force the panel display style to smooth shaded if it's not already
// this ensures that viewport selection behaviour works as if shaded
//
M3dView view;
if (destination.length() &&
{
{
}
}
}
MStatus RenderOverride::cleanup()
{
mCurrentOperation = -1;
}
//
// Image blit used to perform the 'scene render'
//
SceneBlit::SceneBlit(const MString &name)
: MQuadRender( name )
, mShaderInstance(NULL)
, mColorTextureChanged(false)
, mDepthTextureChanged(false)
, mDepthStencilState(NULL)
{
mColorTexture.texture = NULL;
mDepthTexture.texture = NULL;
}
SceneBlit::~SceneBlit()
{
if (!renderer)
return;
// Release any shader used
if (mShaderInstance)
{
const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();
if (shaderMgr)
{
shaderMgr->releaseShader(mShaderInstance);
}
mShaderInstance = NULL;
}
// Release any state
if (mDepthStencilState)
{
mDepthStencilState = NULL;
}
}
const MHWRender::MShaderInstance * SceneBlit::shader()
{
if (!mShaderInstance)
{
const MHWRender::MShaderManager* shaderMgr = renderer ? renderer->getShaderManager() : NULL;
if (shaderMgr)
{
// Create the shader.
//
// The default shader technique will blit color and depth textures to the
// the output color and depth buffers respectively. The values in the depth
// texture are expected to be normalized.
//
// The flag 'showDepthAsColor' can be set to switch to the "DepthToColor"
// technique which will route the depth texture to the color buffer.
// This can be used for visualizing or debugging the contents of the depth texture.
//
bool showDepthAsColor = false;
mShaderInstance = shaderMgr->getEffectsFileShader( "mayaBlitColorDepth", showDepthAsColor ? "DepthToColor" : "" );
}
}
if (mShaderInstance)
{
// If texture changed then bind new texture to the shader
//
if (mColorTextureChanged)
{
status = mShaderInstance->setParameter("gColorTex", mColorTexture);
mColorTextureChanged = false;
}
if (status == MStatus::kSuccess && mDepthTextureChanged)
{
status = mShaderInstance->setParameter("gDepthTex", mDepthTexture);
mDepthTextureChanged = false;
}
}
if (status != MStatus::kSuccess)
{
return NULL;
}
return mShaderInstance;
}
MHWRender::MClearOperation & SceneBlit::clearOperation()
{
mClearOperation.setClearGradient( false );
mClearOperation.setMask( (unsigned int) MHWRender::MClearOperation::kClearAll );
return mClearOperation;
}
//
// Want to have this state override set to override the default
// depth stencil state which disables depth writes.
//
const MHWRender::MDepthStencilState *SceneBlit::depthStencilStateOverride()
{
if (!mDepthStencilState)
{
desc.depthEnable = true;
desc.depthWriteEnable = true;
}
return mDepthStencilState;
}
// Maya UI draw operation. Draw all UI except for a few exclusion
//
UIDraw::UIDraw(const MString& name)
{
}
UIDraw::~UIDraw()
{
}
UIDraw::renderFilterOverride()
{
return MHWRender::MSceneRender::kRenderNonShadedItems;
}
MUint64
UIDraw::getObjectTypeExclusions()
{
// Exclude drawing the grid and image planes
}
UIDraw::clearOperation()
{
// Disable clear since we don't want to clobber the scene colour blit.
mClearOperation.setMask((unsigned int)MHWRender::MClearOperation::kClearNone);
return mClearOperation;
}
} //namespace