(題圖來自StackOverflow)

年都過完了,貌似之前寫的東西都忘記得差不多了。。。汗)

其實在這段日子裡,雖然文章沒有更新多少,代碼是寫了很多的。主要是在填各種以前留下的坑。

之前在

陳文禮:從零開始手敲次世代遊戲引擎(七十三)?

zhuanlan.zhihu.com
圖標

裡面提到的問題1,「Objective C++似乎還是有些奇怪的問題」,這個已經找到問題點和解決方法了。問題的根本在於,我在一些C++和Object C共同參照的頭文件裡面,簡單粗暴地使用了下面的這種通過預定義宏來判斷的方式,來避免Object C的對象無法在C++當中編譯的問題:

#pragma once
#include "GraphicsManager.hpp"

#ifdef __OBJC__
#include "MetalView.h"
#include "Metal2Renderer.h"
#endif

namespace My {
class Metal2GraphicsManager : public GraphicsManager
{
public:
int Initialize() final;
void Finalize() final;

void Draw() final;
void Present() final;

void UseShaderProgram(const int32_t shaderProgram) final;

void DrawBatch(const std::vector<std::shared_ptr<DrawBatchContext>>& batches) final;

int32_t GenerateCubeShadowMapArray(const uint32_t width, const uint32_t height, const uint32_t count) final;
int32_t GenerateShadowMapArray(const uint32_t width, const uint32_t height, const uint32_t count) final;
void BeginShadowMap(const Light& light, const int32_t shadowmap, const uint32_t width, const uint32_t height, const uint32_t layer_index) final;
void EndShadowMap(const int32_t shadowmap, const uint32_t layer_index) final;
void SetShadowMaps(const Frame& frame) final;
void DestroyShadowMap(int32_t& shadowmap) final;

// skybox
void SetSkyBox(const DrawFrameContext& context) final;
void DrawSkyBox() final;

// compute shader tasks
int32_t GenerateAndBindTextureForWrite(const char* id, const uint32_t width, const uint32_t height) final;
void Dispatch(const uint32_t width, const uint32_t height, const uint32_t depth) final;

#ifdef __OBJC__
void SetRenderer(Metal2Renderer* renderer) { m_pRenderer = renderer; }
#endif

private:
void BeginScene(const Scene& scene) final;
void EndScene() final;

void BeginFrame() final;
void EndFrame() final;

void BeginPass() final;
void EndPass() final;

void BeginCompute() final;
void EndCompute() final;

void initializeGeometries(const Scene& scene);
void initializeSkyBox(const Scene& scene);
void initializeTerrain(const Scene& scene);

void SetPerFrameConstants(const DrawFrameContext& context) final;
void SetPerBatchConstants(const std::vector<std::shared_ptr<DrawBatchContext>>& batches) final;
void SetLightInfo(const LightInfo& lightInfo) final;

private:
#ifdef __OBJC__
Metal2Renderer* m_pRenderer;
#endif
};
}

很厲害是不是。我真是天才。一切看起來完美,但是這就是導致各種奇奇怪怪問題的元兇。為什麼?因為這導致在ObjC++當中的這個類,與C++當中的這個類,其實並不是同一個東西!尺寸不一樣大!

所以,當這樣的對象在ObjC++和C++之間傳遞的時候,就發生了各種各樣奇奇怪怪的問題。

所以,基本功很重要,是不是。

正確的做法,參考下面這篇文章:

Mixing Objective-C, C++ and Objective-C++: an Updated Summary?

philjordan.eu

根據這篇文章當中介紹的方法,上面這個頭文件修改為:

#pragma once
#include "portable.hpp"
#include "GraphicsManager.hpp"

OBJC_CLASS(Metal2Renderer);

namespace My {
class Metal2GraphicsManager : public GraphicsManager
{
public:
int Initialize() final;
void Finalize() final;

void Draw() final;
void Present() final;

void UseShaderProgram(const IShaderManager::ShaderHandler shaderProgram) final;

void DrawBatch(const std::vector<std::shared_ptr<DrawBatchContext>>& batches) final;

int32_t GenerateCubeShadowMapArray(const uint32_t width, const uint32_t height, const uint32_t count) final;
int32_t GenerateShadowMapArray(const uint32_t width, const uint32_t height, const uint32_t count) final;
void BeginShadowMap(const Light& light, const int32_t shadowmap, const uint32_t width, const uint32_t height, const int32_t layer_index) final;
void EndShadowMap(const int32_t shadowmap, const int32_t layer_index) final;
void SetShadowMaps(const Frame& frame) final;
void DestroyShadowMap(int32_t& shadowmap) final;

// skybox
void SetSkyBox(const DrawFrameContext& context) final;
void DrawSkyBox() final;

// compute shader tasks
int32_t GenerateAndBindTextureForWrite(const char* id, const uint32_t slot_index, const uint32_t width, const uint32_t height) final;
void Dispatch(const uint32_t width, const uint32_t height, const uint32_t depth) final;

void SetRenderer(Metal2Renderer* renderer) { m_pRenderer = renderer; }

private:
void BeginScene(const Scene& scene) final;
void EndScene() final;

void BeginFrame() final;
void EndFrame() final;

void BeginPass() final;
void EndPass() final;

void BeginCompute() final;
void EndCompute() final;

void initializeGeometries(const Scene& scene);
void initializeSkyBox(const Scene& scene);
void initializeTerrain(const Scene& scene);

void SetPerFrameConstants(const DrawFrameContext& context) final;
void SetPerBatchConstants(const std::vector<std::shared_ptr<DrawBatchContext>>& batches) final;
void SetLightInfo(const LightInfo& lightInfo) final;

private:
Metal2Renderer* m_pRenderer;
};
}

其中主要的黑魔法在

OBJC_CLASS(Metal2Renderer);

這個宏的定義是這樣的:

#ifdef __OBJC__
#define OBJC_CLASS(name) @class name
#else
#define OBJC_CLASS(name) typedef struct objc_object name
#endif

這樣就可以完美地解決這個問題了。當然,要注意,在所有C++會參照的頭文件當中,我們不要使用id<>,或者是ObjC對象的具體方法,而只是保存到ObjC對象的指針。

因此,相對於其它圖形API,我們並沒有直接在Metal2GraphicsManager當中調用Metal2的介面,而只是保存了一個指向ObjC對象Metal2Renderer的指針。當Metal2GraphicsManager的特定方法被調用時,我們只是簡單地通過這個指針把請求傳遞給Metal2Renderer對象,比如像下面這樣:

void Metal2GraphicsManager::UseShaderProgram(const IShaderManager::ShaderHandler shaderProgram)
{
[m_pRenderer useShaderProgram:shaderProgram];
}

void Metal2GraphicsManager::SetPerFrameConstants(const DrawFrameContext& context)
{
[m_pRenderer setPerFrameConstants:context];
}

void Metal2GraphicsManager::SetPerBatchConstants(const std::vector<std::shared_ptr<DrawBatchContext>>& batches)
{
[m_pRenderer setPerBatchConstants:batches];
}

void Metal2GraphicsManager::SetLightInfo(const LightInfo& lightInfo)
{
[m_pRenderer setLightInfo:lightInfo];
}

void Metal2GraphicsManager::DrawBatch(const std::vector<std::shared_ptr<DrawBatchContext>>& batches)
{
[m_pRenderer drawBatch:batches];
}

這樣就比較乾淨利落地將C++代碼部分和ObjC代碼部分連接了起來,又避免了暴露過多特定語言的細節給另外一個語言。

這個調整已經包括在代碼樹的主幹(master)以及(article_77)的分支當中了。

推薦閱讀:

相关文章