[SOLVED] D3D9: two monitors and mixing FVF/shaders

Problems building or running the engine, queries about how to use features etc.
Post Reply
Fktor
Gnoblar
Posts: 3
Joined: Mon Feb 16, 2015 5:19 pm

[SOLVED] D3D9: two monitors and mixing FVF/shaders

Post by Fktor »

Hello,
In the process of adding Oculus Rift extended mode support to our Ogre-based application I came upon a problem - the scene would render in a strange way if there were two RenderWindows, one on the Rift, one on a monitor.

Upon investigation, it turned out Ogre was drawing materials that should use the fixed-function pipeline using vertex/pixel shaders from other materials.
I've traced it to this place in OgreSceneManager:

Code: Select all

if (pass->hasVertexProgram())
        {
           ...
        }
        else
        {
            // Unbind program?
            if (mDestRenderSystem->isGpuProgramBound(GPT_VERTEX_PROGRAM))
            {
                mDestRenderSystem->unbindGpuProgram(GPT_VERTEX_PROGRAM);
            }
            // Set fixed-function vertex parameters
        }
Code for isGpuProgramBound:

Code: Select all

bool RenderSystem::isGpuProgramBound(GpuProgramType gptype)
    {
        switch(gptype)
        {
        case GPT_VERTEX_PROGRAM:
            return mVertexProgramBound;
        ...
    }
(the code for the pixel shader is similiar)
The Direct3D 9 renderer creates one device per monitor, and vertex programs are bound per device. However, mVertexProgramBound is global for the whole render system. If the render order goes:
  • fixed-function on device 0
    shader on device 0
    fixed-function on device 1
... then when the next frame starts, device 0 has a shader set, but mVertexProgramBound is false.

Am I missing something, or is this a bug?

I should be able to put up a sample for this later is someone is interested.
Last edited by Fktor on Thu Mar 19, 2015 11:21 am, edited 1 time in total.
Fktor
Gnoblar
Posts: 3
Joined: Mon Feb 16, 2015 5:19 pm

Re: D3D9: two monitors and mixing FVF/shaders

Post by Fktor »

EDIT: Also added screenshots.

Here's the code sample. You need two monitors. Move the windows around until the entities start displaying incorretly.
I'm using Ogre 1.9 compiled with Visual Studio 2013, sitting on nVidia 347.52 drivers.

TestProj.h

Code: Select all

#pragma once

#include <OgreRoot.h>
#include <OgreFrameListener.h>

class TestProj : public Ogre::FrameListener
{
protected:
	Ogre::Root* mRoot;

	Ogre::RenderWindow* mWindows[2];
	Ogre::SceneManager* mSceneMgrs[2];
	Ogre::Camera* mCameras[2];
	HWND mHwnd;
public:
	TestProj();
	~TestProj();

	TestProj(const TestProj&a) = delete;
	TestProj& operator=(const TestProj&a) = delete;

	bool go();
	virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
protected:
	bool initOgre(void);
	void createCameras(void);
	void createScenes();
};
TestProj.cpp

Code: Select all

#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "TestProj.h"
#include <Ogre.h>

HINSTANCE globInst;

TestProj::TestProj() : mRoot(NULL), mHwnd(NULL)
{
	for (int i = 0; i < 2; i++)
	{
		mCameras[i] = NULL;
		mSceneMgrs[i] = NULL;
		mWindows[i] = NULL;
	}
}

TestProj::~TestProj()
{
	for (int i = 0; i < 2; i++)
	{
		if (mSceneMgrs[i])
			mRoot->destroySceneManager(mSceneMgrs[i]);
		if (mWindows[i])
			mRoot->destroyRenderTarget(mWindows[i]);
	}

	if (mRoot)
		delete mRoot;
}

bool TestProj::initOgre()
{
	Ogre::String pluginCfg, resCfg;
#ifdef _DEBUG
	pluginCfg = "plugins_d.cfg";
	resCfg = "resources_d.cfg";
#else
	pluginCfg = "plugins.cfg";
	resCfg = "resources.cfg";
#endif
	//1.Init Ogre root
	mRoot = new Ogre::Root(pluginCfg);

	//2.Parse & load config
	Ogre::ConfigFile cfg;
	cfg.load(resCfg);

	Ogre::ConfigFile::SectionIterator seci = cfg.getSectionIterator();

	Ogre::String secName, typeName, archName;
	while (seci.hasMoreElements())
	{
		secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		Ogre::ConfigFile::SettingsMultiMap::iterator i;
		for (i = settings->begin(); i != settings->end(); ++i)
		{
			typeName = i->first;
			archName = i->second;
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName);
		}
	}
	//3.Init render system
	Ogre::RenderSystem* rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem");
	if (!rs)
		return false;
	rs->setConfigOption("Full Screen", "No");
	rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");
	rs->setConfigOption("Multi device memory hint", "Auto hardware buffers management");
	rs->setConfigOption("Resource Creation Policy", "Create on all devices");
	mRoot->setRenderSystem(rs);

	return true;
}

bool TestProj::go()
{
	if (!initOgre())
		return false;

	//Create windows
	mWindows[0] = mRoot->initialise(true, "Window 1");
	mWindows[0]->setDeactivateOnFocusChange(false);

	Ogre::NameValuePairList opt;
	opt["left"] = std::to_string(2000);
	opt["top"] = std::to_string(200);

	mWindows[1] = Ogre::Root::getSingleton().createRenderWindow("test", 800, 600, false, &opt);
	mWindows[1]->setDeactivateOnFocusChange(false);

	//Init resources
	// Set default mipmap level (note: some APIs ignore this)
	Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

	//Create scenes
	for (int i = 0; i < 2; i++)
	{
		mSceneMgrs[i] = mRoot->createSceneManager(Ogre::ST_GENERIC);
	}
	createCameras();
	createScenes();

	//run
	mRoot->addFrameListener(this);

	mRoot->startRendering();

	return true;
}

bool TestProj::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	if (mWindows[0]->isClosed() || mWindows[1]->isClosed())
		return false;

	return true;
}

void TestProj::createCameras(void)
{
	for (int i = 0; i < 2; i++)
	{
		mCameras[i] = mSceneMgrs[i]->createCamera("PlayerCam");

		// Position it at 500 in Z direction
		mCameras[i]->setPosition(Ogre::Vector3(0, 0, 80));
		// Look back along -Z
		mCameras[i]->lookAt(Ogre::Vector3(0, 0, -300));
		mCameras[i]->setNearClipDistance(0.01f);

		Ogre::Viewport* vp = mWindows[i]->addViewport(mCameras[i]);
		vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0));
		vp->setOverlaysEnabled(false);

		// Alter the camera aspect ratio to match the viewport
		mCameras[i]->setAspectRatio(
			Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
	}
}

void TestProj::createScenes()
{
	//create two identical scenes
	for (int i = 0; i < 2; i++)
	{
		Ogre::SceneNode * rootNode;
		rootNode = mSceneMgrs[i]->getRootSceneNode();

		mSceneMgrs[i]->setAmbientLight(Ogre::ColourValue(0.1f, 0.1f, 0.1f));

		Ogre::Light* dirLight;
		dirLight = mSceneMgrs[i]->createLight();
	
		dirLight->setType(Ogre::Light::LightTypes::LT_DIRECTIONAL);
		dirLight->setDirection(Ogre::Vector3(0.8f, -0.7f, 0.1f));
		dirLight->setDiffuseColour(Ogre::ColourValue(0.7f, 0.7f, 0.7f));
		rootNode->createChildSceneNode()->attachObject(dirLight);

		Ogre::Entity * head;
		Ogre::SceneNode* headNode;
		head = mSceneMgrs[i]->createEntity("ogrehead.mesh");

		head->setMaterialName("BaseWhite");
		head->setRenderQueueGroup(Ogre::RENDER_QUEUE_MAIN);

		headNode = rootNode->createChildSceneNode();
		headNode->attachObject(head);
		headNode->setPosition(Ogre::Vector3(-20.0f, 0.0f, 0.0f));
	}

	//add a program shaded entity to one of the scenes

	Ogre::Entity * penguin;
	Ogre::SceneNode * penguinNode;

	penguin = mSceneMgrs[1]->createEntity("penguin", "penguin.mesh");

	penguin->setRenderQueueGroup(Ogre::RENDER_QUEUE_6);
	penguin->setMaterialName("SimpleShader");
	penguinNode = mSceneMgrs[1]->getRootSceneNode()->createChildSceneNode();
	penguinNode->attachObject(penguin);
	penguinNode->setPosition(Ogre::Vector3(30.0f, 0.0f, 0.0f));

}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CLOSE:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
}

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
	INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
	int main(int argc, char *argv[])
#endif
	{
		globInst = hInst;
		// Create application object
		TestProj app;

		try {
			app.go();
		}
		catch (Ogre::Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
			MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
			std::cerr << "An exception has occured: " <<
				e.getFullDescription().c_str() << std::endl;
#endif
		}

		return 0;
	}

#ifdef __cplusplus
}
#endif
I could submit a patch, but would like someone to at least reproduce this ;)
The plan would be to:
1. Keep vertexProgramBound and fragmentProgramBound per device
2. Override isGpuProgramBound in OgreD3D9RenderSystem to query the active device
3. Rewrite all references to mVertexProgramBound and mFragmentProgramBound in OgreD3D9RenderSystem to also query the device (this is the tricky part)
Attachments
Different monitors
Different monitors
Same monitor
Same monitor
Fktor
Gnoblar
Posts: 3
Joined: Mon Feb 16, 2015 5:19 pm

Re: D3D9: two monitors and mixing FVF/shaders

Post by Fktor »

Created a pull request with a fix for this:
https://bitbucket.org/sinbad/ogre/pull-request/489/

EDIT: ...and it got accepted, so I'm marking this resolved.
Post Reply