コード例 #1
0
void opengl_tnl_set_material(material* material_info, bool set_base_map, bool set_clipping)
{
	int shader_handle = material_info->get_shader_handle();
	int base_map = material_info->get_texture_map(TM_BASE_TYPE);
	vec4 clr = material_info->get_color();

	Assert(shader_handle >= 0);

	opengl_shader_set_current(shader_handle);

	if (material_info->has_buffer_blend_modes()) {
		Assertion(GLAD_GL_ARB_draw_buffers_blend != 0,
				  "Buffer blend modes are not supported at the moment! Query the capability before using this feature.");

		auto enable_blend = false;
		for (auto i = 0; i < (int) material::NUM_BUFFER_BLENDS; ++i) {
			auto mode = material_info->get_blend_mode(i);

			GL_state.SetAlphaBlendModei(i, mode);
			enable_blend = enable_blend || mode != ALPHA_BLEND_NONE;
		}
		GL_state.Blend(enable_blend ? GL_TRUE : GL_FALSE);
	} else {
		GL_state.SetAlphaBlendMode(material_info->get_blend_mode());
	}
	GL_state.SetZbufferType(material_info->get_depth_mode());

	gr_set_cull(material_info->get_cull_mode() ? 1 : 0);

	gr_zbias(material_info->get_depth_bias());

	gr_set_fill_mode(material_info->get_fill_mode());

	gr_set_texture_addressing(material_info->get_texture_addressing());

	if (set_clipping) {
		// Only set the clipping state if explicitly requested by the caller to avoid unnecessary state changes
		auto& clip_params = material_info->get_clip_plane();
		if (!clip_params.enabled) {
			GL_state.ClipDistance(0, false);
		} else {
			Assertion(Current_shader != NULL && (Current_shader->shader == SDR_TYPE_MODEL
				|| Current_shader->shader == SDR_TYPE_PASSTHROUGH_RENDER
				|| Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL),
					  "Clip planes are not supported by this shader!");

			GL_state.ClipDistance(0, true);
		}
	}

	GL_state.StencilMask(material_info->get_stencil_mask());

	auto& stencilFunc = material_info->get_stencil_func();
	GL_state.StencilFunc(convertComparisionFunction(stencilFunc.compare), stencilFunc.ref, stencilFunc.mask);

	auto& frontStencilOp = material_info->get_front_stencil_op();
	GL_state.StencilOpSeparate(GL_FRONT,
							   convertStencilOp(frontStencilOp.stencilFailOperation),
							   convertStencilOp(frontStencilOp.depthFailOperation),
							   convertStencilOp(frontStencilOp.successOperation));
	auto& backStencilOp = material_info->get_back_stencil_op();
	GL_state.StencilOpSeparate(GL_BACK,
							   convertStencilOp(backStencilOp.stencilFailOperation),
							   convertStencilOp(backStencilOp.depthFailOperation),
							   convertStencilOp(backStencilOp.successOperation));

	GL_state.StencilTest(material_info->is_stencil_enabled() ? GL_TRUE : GL_FALSE);

	auto& color_mask = material_info->get_color_mask();
	GL_state.ColorMask(color_mask.x, color_mask.y, color_mask.z, color_mask.w);

	// This is only needed for the passthrough shader
	uint32_t array_index = 0;
	if ( set_base_map && base_map >= 0 ) {
		float u_scale, v_scale;

		if ( !gr_opengl_tcache_set(base_map, material_info->get_texture_type(), &u_scale, &v_scale, &array_index) ) {
			mprintf(("WARNING: Error setting bitmap texture (%i)!\n", base_map));
		}
	}

	if ( Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL ) {
		opengl_shader_set_default_material(base_map >= 0,
										   material_info->get_texture_type() == TCACHE_TYPE_AABITMAP,
										   &clr,
										   material_info->get_color_scale(),
										   array_index,
										   material_info->get_clip_plane());
	}
}
コード例 #2
0
ファイル: rsx_pgraph.cpp プロジェクト: zod331/nucleus
void PGRAPH::Begin(Primitive primitive) {
    // Set surface
    setSurface();

    // Set viewport
    gfx::Viewport viewportRect = { viewport.x, viewport.y, viewport.width, viewport.height, 0.0f, 1.0f };
    gfx::Rectangle scissorRect = { scissor.x, scissor.y, scissor.width, scissor.height };
    cmdBuffer->cmdSetViewports(1, &viewportRect);
    cmdBuffer->cmdSetScissors(1, &scissorRect);

    // Hashing
    auto vpData = &vpe.data[vpe.start];
    auto vpHash = HashVertexProgram(vpData);
    auto fpData = memory->ptr<rsx_fp_instruction_t>((fp_location ? rsx->get_ea(0x0) : 0xC0000000) + fp_offset);
    auto fpHash = HashFragmentProgram(fpData);
    auto pipelineHash = hashStruct(pipeline) ^ vpHash ^ fpHash;

    if (cachePipeline.find(pipelineHash) == cachePipeline.end()) {
        const auto& p = pipeline;
        if (cacheVP.find(vpHash) == cacheVP.end()) {
            auto vp = std::make_unique<RSXVertexProgram>();
            vp->decompile(vpData);
            vp->compile(graphics.get());
            cacheVP[vpHash] = std::move(vp);
        }
        if (cacheFP.find(fpHash) == cacheFP.end()) {
            auto fp = std::make_unique<RSXFragmentProgram>();
            fp->decompile(fpData);
            fp->compile(graphics.get());
            cacheFP[fpHash] = std::move(fp);
        }
        gfx::PipelineDesc pipelineDesc = {};
        pipelineDesc.formatDSV = convertFormat(surface.depthFormat);
        pipelineDesc.numCBVs = 2;
        pipelineDesc.numSRVs = RSX_MAX_TEXTURES;
        pipelineDesc.vs = cacheVP[vpHash]->shader;
        pipelineDesc.ps = cacheFP[fpHash]->shader;

        pipelineDesc.rsState.fillMode = gfx::FILL_MODE_SOLID;
        pipelineDesc.rsState.cullMode = p.cull_face_enable ? convertCullMode(p.cull_mode) : gfx::CULL_MODE_NONE;
        pipelineDesc.rsState.frontCounterClockwise = convertFrontFace(p.front_face);
        pipelineDesc.rsState.depthEnable = p.depth_test_enable;
        pipelineDesc.rsState.depthWriteMask = p.depth_mask ? gfx::DEPTH_WRITE_MASK_ALL : gfx::DEPTH_WRITE_MASK_ZERO;
        pipelineDesc.rsState.depthFunc = convertCompareFunc(p.depth_func);
        pipelineDesc.rsState.stencilEnable = p.stencil_test_enable;
        pipelineDesc.rsState.stencilReadMask = p.stencil_func_mask;
        pipelineDesc.rsState.stencilWriteMask = p.stencil_mask;
        pipelineDesc.rsState.frontFace.stencilOpFail = convertStencilOp(p.stencil_op_fail);
        pipelineDesc.rsState.frontFace.stencilOpZFail = convertStencilOp(p.stencil_op_zfail);
        pipelineDesc.rsState.frontFace.stencilOpPass = convertStencilOp(p.stencil_op_zpass);
        pipelineDesc.rsState.frontFace.stencilFunc = convertCompareFunc(p.stencil_func);
        if (p.two_sided_stencil_test_enable) {
            pipelineDesc.rsState.backFace.stencilOpFail = convertStencilOp(p.stencil_op_fail);
            pipelineDesc.rsState.backFace.stencilOpZFail = convertStencilOp(p.stencil_op_zfail);
            pipelineDesc.rsState.backFace.stencilOpPass = convertStencilOp(p.stencil_op_zpass);
            pipelineDesc.rsState.backFace.stencilFunc = convertCompareFunc(p.stencil_func);
        } else {
            pipelineDesc.rsState.backFace.stencilOpFail = convertStencilOp(p.back_stencil_op_fail);
            pipelineDesc.rsState.backFace.stencilOpZFail = convertStencilOp(p.back_stencil_op_zfail);
            pipelineDesc.rsState.backFace.stencilOpPass = convertStencilOp(p.back_stencil_op_zpass);
            pipelineDesc.rsState.backFace.stencilFunc = convertCompareFunc(p.back_stencil_func);
        }

        pipelineDesc.cbState.colorTarget[0].enableBlend = p.blend_enable;
        pipelineDesc.cbState.colorTarget[0].enableLogicOp = p.logic_op_enable;
        pipelineDesc.cbState.colorTarget[0].blendOp = convertBlendOp(p.blend_equation_rgb);
        pipelineDesc.cbState.colorTarget[0].blendOpAlpha = convertBlendOp(p.blend_equation_alpha);
        pipelineDesc.cbState.colorTarget[0].srcBlend = convertBlend(p.blend_sfactor_rgb);
        pipelineDesc.cbState.colorTarget[0].destBlend = convertBlend(p.blend_dfactor_rgb);
        pipelineDesc.cbState.colorTarget[0].srcBlendAlpha = convertBlend(p.blend_sfactor_alpha);
        pipelineDesc.cbState.colorTarget[0].destBlendAlpha = convertBlend(p.blend_dfactor_alpha);
        pipelineDesc.cbState.colorTarget[0].colorWriteMask = convertColorMask(p.color_mask);
        pipelineDesc.cbState.colorTarget[0].logicOp = convertLogicOp(p.logic_op);
        pipelineDesc.iaState.topology = convertPrimitiveTopology(primitive);
        for (U32 index = 0; index < RSX_MAX_VERTEX_INPUTS; index++) {
            const auto& attr = vpe.attr[index];
            if (!attr.size) {
                continue;
            }
            gfx::Format format = convertVertexFormat(attr.type, attr.size);
            U32 stride = attr.stride;
            pipelineDesc.iaState.inputLayout.push_back({
                index, format, index, 0, stride, 0, gfx::INPUT_CLASSIFICATION_PER_VERTEX, 0 }
            );
        }
        for (U32 i = 0; i < RSX_MAX_TEXTURES; i++) {
            gfx::Sampler sampler = {};
            sampler.filter = gfx::FILTER_MIN_MAG_MIP_LINEAR;
            sampler.addressU = gfx::TEXTURE_ADDRESS_MIRROR;
            sampler.addressV = gfx::TEXTURE_ADDRESS_MIRROR;
            sampler.addressW = gfx::TEXTURE_ADDRESS_MIRROR;
            pipelineDesc.samplers.push_back(sampler);
        }
        cachePipeline[pipelineHash] = std::unique_ptr<gfx::Pipeline>(graphics->createPipeline(pipelineDesc));
    }

    heapResources->reset();
    heapResources->pushVertexBuffer(vpeConstantMemory);
    heapResources->pushVertexBuffer(vtxTransform);

    // Upload VPE constants if necessary
    void* constantsPtr = vpeConstantMemory->map();
    memcpy(constantsPtr, &vpe.constant, sizeof(vpe.constant));
    vpeConstantMemory->unmap();

    // Upload vertex transform matrix if necessary
    if (vertex_transform_dirty) {
        V128* transformPtr = reinterpret_cast<V128*>(vtxTransform->map());
        memset(transformPtr, 0, 4 * sizeof(V128));
        F32 half_cliph = surface.width / 2.0f;
        F32 half_clipv = surface.height / 2.0f;
        transformPtr[0].f32[0] = (viewport_scale.f32[0] / half_cliph);
        transformPtr[1].f32[1] = (viewport_scale.f32[1] / half_clipv);
        transformPtr[2].f32[2] = (viewport_scale.f32[2]);
        transformPtr[0].f32[3] = (viewport_offset.f32[0] - half_cliph) / half_cliph;
        transformPtr[1].f32[3] = (viewport_offset.f32[1] - half_clipv) / half_clipv;
        transformPtr[2].f32[3] = (viewport_offset.f32[2]);
        transformPtr[3].f32[3] = 1.0f;
        vtxTransform->unmap();
    }

    // Set textures
    for (U32 i = 0; i < RSX_MAX_TEXTURES; i++) {
        const auto& tex = texture[i];

        // Dummy texture
        if (!tex.enable) {
            gfx::TextureDesc texDesc = {};
            texDesc.width = 2;
            texDesc.height = 2;
            texDesc.format = gfx::FORMAT_R8G8B8A8_UNORM;
            texDesc.mipmapLevels = 1;
            texDesc.swizzle = TEXTURE_SWIZZLE_ENCODE(
                gfx::TEXTURE_SWIZZLE_VALUE_0,
                gfx::TEXTURE_SWIZZLE_VALUE_0,
                gfx::TEXTURE_SWIZZLE_VALUE_0,
                gfx::TEXTURE_SWIZZLE_VALUE_0
            );
            gfx::Texture* texDescriptor = graphics->createTexture(texDesc);
            heapResources->pushTexture(texDescriptor);
        }

        // Upload real texture
        else {
            auto texFormat = static_cast<TextureFormat>(tex.format & ~RSX_TEXTURE_LN & ~RSX_TEXTURE_UN);

            gfx::TextureDesc texDesc = {};
            texDesc.data = memory->ptr<Byte>((tex.location ? rsx->get_ea(0x0) : 0xC0000000) + tex.offset);
            texDesc.size = tex.width * tex.height;
            texDesc.width = tex.width;
            texDesc.height = tex.height;
            texDesc.format = convertTextureFormat(texFormat);
            texDesc.mipmapLevels = tex.mipmap;
            texDesc.swizzle = convertTextureSwizzle(texFormat);

            switch (texFormat) {
            case RSX_TEXTURE_B8:        texDesc.size *= 1; break;
            case RSX_TEXTURE_A1R5G5B5:  texDesc.size *= 2; break;
            case RSX_TEXTURE_A4R4G4B4:  texDesc.size *= 2; break;
            case RSX_TEXTURE_R5G6B5:    texDesc.size *= 2; break;
            case RSX_TEXTURE_A8R8G8B8:  texDesc.size *= 4; break;
            default:
                assert_always("Unimplemented");
            }

            gfx::Texture* texDescriptor = graphics->createTexture(texDesc);
            heapResources->pushTexture(texDescriptor);
        }
    }

    cmdBuffer->cmdBindPipeline(cachePipeline[pipelineHash].get());
    cmdBuffer->cmdSetHeaps({ heapResources });
    cmdBuffer->cmdSetDescriptor(0, heapResources, 0);
    cmdBuffer->cmdSetDescriptor(1, heapResources, 2);
    cmdBuffer->cmdSetPrimitiveTopology(convertPrimitiveTopology(primitive));
}
コード例 #3
0
ファイル: direct3d12_backend.cpp プロジェクト: zod331/nucleus
Pipeline* Direct3D12Backend::createPipeline(const PipelineDesc& desc) {
    HRESULT hr;
    auto* pipeline = new Direct3D12Pipeline();

    // Root signature parameters
    CD3DX12_DESCRIPTOR_RANGE ranges[2];
    ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, desc.numCBVs, 0);
    ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, desc.numSRVs, 0);

    std::vector<CD3DX12_ROOT_PARAMETER> parameters;
    if (desc.numCBVs) {
        CD3DX12_ROOT_PARAMETER parameter;
        parameter.InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_ALL);
        parameters.push_back(parameter);
    }
    if (desc.numSRVs) {
        CD3DX12_ROOT_PARAMETER parameter;
        parameter.InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_PIXEL);
        parameters.push_back(parameter);
    }

    // Samplers
    std::vector<D3D12_STATIC_SAMPLER_DESC> d3dSamplers(desc.samplers.size());
    for (Size i = 0; i < desc.samplers.size(); i++) {
        const auto& sampler = desc.samplers[i];
        d3dSamplers[i].Filter = convertFilter(sampler.filter);
        d3dSamplers[i].AddressU = convertTextureAddressMode(sampler.addressU);
        d3dSamplers[i].AddressV = convertTextureAddressMode(sampler.addressV);
        d3dSamplers[i].AddressW = convertTextureAddressMode(sampler.addressW);
        d3dSamplers[i].MipLODBias = 0;
        d3dSamplers[i].MaxAnisotropy = 0;
        d3dSamplers[i].ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
        d3dSamplers[i].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
        d3dSamplers[i].MinLOD = 0.0f;
        d3dSamplers[i].MaxLOD = D3D12_FLOAT32_MAX;
        d3dSamplers[i].ShaderRegister = i;
        d3dSamplers[i].RegisterSpace = 0;
        d3dSamplers[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
    }

    // Root signature
    D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
    rootSignatureDesc.NumParameters = parameters.size();
    rootSignatureDesc.pParameters = parameters.data();
    rootSignatureDesc.NumStaticSamplers = d3dSamplers.size();
    rootSignatureDesc.pStaticSamplers = d3dSamplers.data();
    rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
        D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
        D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
        D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS;

    ID3DBlob* signature;
    ID3DBlob* error;
    hr = _D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
    if (FAILED(hr)) {
        LPVOID errorString = error->GetBufferPointer();
        logger.error(LOG_GRAPHICS, "Direct3D12Backend::createPipeline: D3D12SerializeRootSignature failed (0x%X): %s", hr, errorString);
        return nullptr;
    }

    hr = device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&pipeline->rootSignature));
    if (FAILED(hr)) {
        logger.error(LOG_GRAPHICS, "Direct3D12Backend::createPipeline: CreateRootSignature failed (0x%X)", hr);
        return nullptr;
    }

    D3D12_GRAPHICS_PIPELINE_STATE_DESC d3dDesc = {};
    d3dDesc.NodeMask = 1;
    d3dDesc.SampleMask = UINT_MAX;
    d3dDesc.pRootSignature = pipeline->rootSignature;
    d3dDesc.NumRenderTargets = 1;
    d3dDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
    d3dDesc.DSVFormat = convertFormat(desc.formatDSV);
    d3dDesc.SampleDesc.Count = 1;

    // Shaders
    if (desc.vs) {
        auto* d3dShader = static_cast<Direct3D12Shader*>(desc.vs);
        d3dDesc.VS.pShaderBytecode = d3dShader->bytecodeData;
        d3dDesc.VS.BytecodeLength = d3dShader->bytecodeSize;
    }
    if (desc.hs) {
        auto* d3dShader = static_cast<Direct3D12Shader*>(desc.hs);
        d3dDesc.HS.pShaderBytecode = d3dShader->bytecodeData;
        d3dDesc.HS.BytecodeLength = d3dShader->bytecodeSize;
    }
    if (desc.ds) {
        auto* d3dShader = static_cast<Direct3D12Shader*>(desc.ds);
        d3dDesc.DS.pShaderBytecode = d3dShader->bytecodeData;
        d3dDesc.DS.BytecodeLength = d3dShader->bytecodeSize;
    }
    if (desc.gs) {
        auto* d3dShader = static_cast<Direct3D12Shader*>(desc.gs);
        d3dDesc.GS.pShaderBytecode = d3dShader->bytecodeData;
        d3dDesc.GS.BytecodeLength = d3dShader->bytecodeSize;
    }
    if (desc.ps) {
        auto* d3dShader = static_cast<Direct3D12Shader*>(desc.ps);
        d3dDesc.PS.pShaderBytecode = d3dShader->bytecodeData;
        d3dDesc.PS.BytecodeLength = d3dShader->bytecodeSize;
    }

    // IA state
    std::vector<D3D12_INPUT_ELEMENT_DESC> d3dInputElements;
    for (const auto& element : desc.iaState.inputLayout) {
        DXGI_FORMAT format = convertFormat(element.format);
        D3D12_INPUT_CLASSIFICATION inputClassification = convertInputClassification(element.inputClassification);
        d3dInputElements.emplace_back(D3D12_INPUT_ELEMENT_DESC{
            "INPUT", element.semanticIndex, format, element.inputSlot, element.offset,
            inputClassification, element.instanceStepRate
        });
    }
    d3dDesc.InputLayout.NumElements = d3dInputElements.size();
    d3dDesc.InputLayout.pInputElementDescs = d3dInputElements.data();
    d3dDesc.PrimitiveTopologyType = convertPrimitiveTopologyType(desc.iaState.topology);

    // RS state
    d3dDesc.RasterizerState.FillMode = convertFillMode(desc.rsState.fillMode);
    d3dDesc.RasterizerState.CullMode = convertCullMode(desc.rsState.cullMode);
    d3dDesc.RasterizerState.FrontCounterClockwise = desc.rsState.frontCounterClockwise;
    d3dDesc.RasterizerState.DepthClipEnable = TRUE; // TODO

    d3dDesc.DepthStencilState.DepthEnable = desc.rsState.depthEnable;
    d3dDesc.DepthStencilState.DepthWriteMask = convertDepthWriteMask(desc.rsState.depthWriteMask);
    d3dDesc.DepthStencilState.DepthFunc = convertComparisonFunc(desc.rsState.depthFunc);
    d3dDesc.DepthStencilState.StencilEnable = desc.rsState.stencilEnable;
    d3dDesc.DepthStencilState.StencilReadMask = desc.rsState.stencilReadMask;
    d3dDesc.DepthStencilState.StencilWriteMask = desc.rsState.stencilWriteMask;
    d3dDesc.DepthStencilState.FrontFace.StencilFailOp = convertStencilOp(desc.rsState.frontFace.stencilOpFail);
    d3dDesc.DepthStencilState.FrontFace.StencilDepthFailOp = convertStencilOp(desc.rsState.frontFace.stencilOpZFail);
    d3dDesc.DepthStencilState.FrontFace.StencilPassOp = convertStencilOp(desc.rsState.frontFace.stencilOpPass);
    d3dDesc.DepthStencilState.FrontFace.StencilFunc = convertComparisonFunc(desc.rsState.frontFace.stencilFunc);
    d3dDesc.DepthStencilState.BackFace.StencilFailOp = convertStencilOp(desc.rsState.backFace.stencilOpFail);
    d3dDesc.DepthStencilState.BackFace.StencilDepthFailOp = convertStencilOp(desc.rsState.backFace.stencilOpZFail);
    d3dDesc.DepthStencilState.BackFace.StencilPassOp = convertStencilOp(desc.rsState.backFace.stencilOpPass);
    d3dDesc.DepthStencilState.BackFace.StencilFunc = convertComparisonFunc(desc.rsState.backFace.stencilFunc);

    // CB state
    d3dDesc.BlendState.AlphaToCoverageEnable = desc.cbState.enableAlphaToCoverage;
    d3dDesc.BlendState.IndependentBlendEnable = desc.cbState.enableIndependentBlend;
    UINT sizeColorTargetBlendArray = d3dDesc.BlendState.IndependentBlendEnable ? 8 : 1;
    for (UINT i = 0; i < sizeColorTargetBlendArray; i++) {
        const auto& source = desc.cbState.colorTarget[i];
        auto& d3dTarget = d3dDesc.BlendState.RenderTarget[i];
        d3dTarget.BlendEnable = source.enableBlend;
        d3dTarget.LogicOpEnable = source.enableLogicOp;
        d3dTarget.SrcBlend = convertBlend(source.srcBlend);
        d3dTarget.DestBlend = convertBlend(source.destBlend);
        d3dTarget.BlendOp = convertBlendOp(source.blendOp);
        d3dTarget.SrcBlendAlpha = convertBlend(source.srcBlendAlpha);
        d3dTarget.DestBlendAlpha = convertBlend(source.destBlendAlpha);
        d3dTarget.BlendOpAlpha = convertBlendOp(source.blendOpAlpha);
        d3dTarget.LogicOp = convertLogicOp(source.logicOp);
        d3dTarget.RenderTargetWriteMask = convertColorWriteMask(source.colorWriteMask);
    }

    hr = device->CreateGraphicsPipelineState(&d3dDesc, IID_PPV_ARGS(&pipeline->state));
    if (FAILED(hr)) {
        logger.error(LOG_GRAPHICS, "Direct3D12Backend::createPipeline: CreateGraphicsPipelineState failed (0x%X)", hr);
        return nullptr;
    }
    return pipeline;
}