【DirectX11】【学习笔记(11)续】显示文本(代码实现)

一些补充:

Adapter翻译成适配器更贴切

An adapter is an abstraction of the hardware and the software capability of your computer. There are generally many adapters on your machine. Some devices are implemented in hardware (like your video card) and some are implemented in software (like the Direct3D reference rasterizer). Adapters implement functionality used by a graphic application. The following diagram shows a system with a single computer, two adapters (video cards), and three output monitors.

对DXGI的一些概述

举个例子,在以前图形子系统都归D3D,结果D3D8/D3D9分别有一套代码用来管理swap chain。在Vista+里,图形API越来越多,D3D9/D3D10/D3D11/D3D12,都来一套swap chain太没意义了。于是重构成所有API可以共享一份swap chain的代码,这就放在DXGI。除此之外,窗口全屏化之类的事情也都归DXGI了,你可以认为屏幕输出的部分都归DXGI。

来自知乎

链接:https://www.zhihu.com/question/36501678/answer/67786884

Microsoft DirectX Graphics Infrastructure (DXGI) recognizes that some parts of graphics evolve more slowly than others. The primary goal of DXGI is to manage low-level tasks that can be independent of the DirectX graphics runtime. DXGI provides a common framework for future graphics components; the first component that takes advantage of DXGI is Microsoft Direct3D 10.

In previous versions of Direct3D, low-level tasks like enumeration of hardware devices, presenting rendered frames to an output, controlling gamma, and managing a full-screen transition were included in the Direct3D runtime. These tasks are now implemented in DXGI.

来自msdn

 

New Includes and Libraries

//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
///**************new**************
#pragma comment (lib, "D3D10_1.lib")
#pragma comment (lib, "DXGI.lib")
#pragma comment (lib, "D2D1.lib")
#pragma comment (lib, "dwrite.lib")
///**************new**************

#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <xnamath.h>
///**************new**************
#include <D3D10_1.h>
#include <DXGI.h>
#include <D2D1.h>
#include <sstream>
#include <dwrite.h>
///**************new**************

Global Declarations

ID3D10Device1 *d3d101Device;    
IDXGIKeyedMutex *keyedMutex11;
IDXGIKeyedMutex *keyedMutex10;    
ID2D1RenderTarget *D2DRenderTarget;    
ID2D1SolidColorBrush *Brush;
ID3D11Texture2D *BackBuffer11;
ID3D11Texture2D *sharedTex11;    
ID3D11Buffer *d2dVertBuffer;
ID3D11Buffer *d2dIndexBuffer;
ID3D11ShaderResourceView *d2dTexture;
IDWriteFactory *DWriteFactory;
IDWriteTextFormat *TextFormat;

std::wstring printText;

最后一个string变量会把我们的文本内容传给d2d

New Functions

bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter);
void InitD2DScreenTexture();
void RenderText(std::wstring text);

第一个函数会用一个shared surface来初始化D3D10.1和D2D,和Direct Write,唯一的参数是一个容器,我们必须确保容器和D3D11是同一个。

第二个是我们创建一个square和一个shader resource view,这样我们可以把第一个函数创建好的shared texture渲染到上面

Enumerate the First Adapter

IDXGIFactory1 *DXGIFactory;

HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory);    
    
IDXGIAdapter1 *Adapter;

hr = DXGIFactory->EnumAdapters1(0, &Adapter);

DXGIFactory->Release();    

首先我们创建一个DXGIFactory.用它来枚举一个容器。我i们只需要第一个容器。所以函数的第一个参数为0

Initialize D3D 11

创建设备和交换链。这个函数我们之前用过多次,但是这里需要修改一下他们的参数,因为我们同时创建了两个设备

第一个参数:指定为我们创建的Adapter

第二个参数是我们使用的设备类型,之前我们使用的是硬件设备,但是现在我们需要设为D3D_DRIVER_TYPE_UNKNOWN

不然会造成错误。

第四个参数:我们指定两个flags。第一个是告诉设备吐出有关设备的额外信息。第二个是使设备和D2D兼容。

然后我们把创建好的容器传入下面函数。调用完成后,再释放容器。

InitD2D_D3D101_DWrite(Adapter);
//Release the Adapter interface
Adapter->Release();

SwapChain Format

我们还需要更改SwapChain的格式。使之和D2D兼容。

bufferDesc.Width = Width;
bufferDesc.Height = Height;
bufferDesc.RefreshRate.Numerator = 60;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

Initializing D3D 10.1, D2D, and DirectWrite Using a Shared Surface

Initializing D3D 10.1

hr = D3D10CreateDevice1(Adapter, D3D10_DRIVER_TYPE_HARDWARE, NULL,D3D10_CREATE_DEVICE_DEBUG |    D3D10_CREATE_DEVICE_BGRA_SUPPORT,
    D3D10_FEATURE_LEVEL_9_3, D3D10_1_SDK_VERSION, &d3d101Device    );

首先我们创建一个D3D10.1设备,这个和创建D3D11类似

Create a D3D 11 2D Texture

接着我们创建一个D3D11的Texture,让它在多个API之间调用。

唯一要注意的使我们需要把它的格式设置成:DXGI_FORMAT_B8G8R8A8_UNORM

以及给MiscFlags指定D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX

D3D11_TEXTURE2D_DESC sharedTexDesc;    

ZeroMemory(&sharedTexDesc, sizeof(sharedTexDesc));

sharedTexDesc.Width = Width;
sharedTexDesc.Height = Height;    
sharedTexDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sharedTexDesc.MipLevels = 1;    
sharedTexDesc.ArraySize = 1;
sharedTexDesc.SampleDesc.Count = 1;
sharedTexDesc.Usage = D3D11_USAGE_DEFAULT;
sharedTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;    
sharedTexDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;    

hr = d3d11Device->CreateTexture2D(&sharedTexDesc, NULL, &sharedTex11); 

D3D 11 Keyed Mutex

我们需要给D3D11设备创建一个keyed mutex,好让设备可以获取贴图。

hr = sharedTex11->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex11);    

Getting a Handle to the D3D 11 Texture

因为D3D10.1不能直接获取D3D11 texture。我们需要曲线救国。。。

我们可以创建一个IDXGIResource来保存到D3D11 texture的指针。

然后创建一个到resource的句柄,这样D3D10.1才可以获得它。

接着我们释放shared resource,因为我们已经有了到texture的句柄

IDXGIResource *sharedResource10;
HANDLE sharedHandle10;    

hr = sharedTex11->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedResource10);

hr = sharedResource10->GetSharedHandle(&sharedHandle10);    

sharedResource10->Release();

Get a Keyed Mutex to the D3D 11 Texture for D3D 10.1

我i们现在需要为D3D10.1设备创建到D3D11 Texture 的互斥锁。

首先我们通过打开shared resource,把指针存在IDXGISurface1 对象中。

D3D10.1和D2D都会在surface上进行渲染。

拿到D3D11 texture的指针之后,我们对surface来获取一个keyed mutex,把指针存进IDXGIKeyedMutex对象。

总结一下我们做了什么:为D3D11创建贴图 keyed mutex。为D3D10.1 把贴图转换成surface object。对surface object来设置一

个keyed mutex

Initializing D2D

通过调用D2D1CreateFactory来创建一个D2D resource。

第一个参数:是否指定多线程,

第二个参数:存储resource的对象接口

第三个参数:接口对象。(返回值)

D2D Render Target Properties

填充D2D render target 属性描述结构

struct D2D1_RENDER_TARGET_PROPERTIES {
  D2D1_RENDER_TARGET_TYPE  type;
  D2D1_PIXEL_FORMAT        pixelFormat;
  FLOAT                    dpiX;
  FLOAT                    dpiY;
  D2D1_RENDER_TARGET_USAGE usage;
  D2D1_FEATURE_LEVEL       minLevel;
};

type - 指定软件还是硬件渲染,我们这里用D2D1_RENDER_TARGET_TYPE_HARDWARE

pixelFormat - 指定像素格式和alpha模式

dpiX - 水平分辨率

dpiY- 垂直分辨率

usage - 指定target如何“remote”(我也没看明白)

minLevel - 指定feature levels

D2D1::PixelFormat() 函数会直接返回一个D2D Pixel format。

函数有两个参数:第一个是render target的大小和颜色通道设置

第二个是alphamode,这个参数解释了render target的alpha channel如何插值。

我们指定D2D1_ALPHA_MODE_PREMULTIPLIED,这样画的东西都是不透明的。

Creating the D2D Render Target

因为我们需要和D3D10.1分享render target所以我们需要创建DXGI render target

通过函数ID2D1Factory::CreateDxgiSurfaceRenderTarget(). (参数很简单,就不介绍了)

为了渲染到D3D11 texture上,我们首先用D2D渲染到D2D的render target上。这个render target是一个DXGI surface ,也就是指向D3D11 texture的一个指针。为什么这么麻烦呢,是因为D2D和D3D11不兼容。

当我们创建好了D2D的render target之后,我们就不再需要shared surface了。

The D2D Brush

hr = D2DRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 0.0f, 1.0f), &Brush);

指定颜色和对象即可。

Initializing DirectWrite

D2D可以被是做一个绘画工具,而DirectWrite 可以被视作一个画家。

DirectWrite会告诉D2D如何绘制字体。

我们线创建一个DWFactory,这个用来创建我们的文本格式。

hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
    reinterpret_cast(&DWriteFactory));

第一个参数是说明factory是否被分享使用。当然,我们要和D2D一起用。

Creating a Font Format

用上一步创建的DW Factory来创建字体格式,

HRESULT CreateTextFormat(
  [in]   const WCHAR * fontFamilyName,
         IDWriteFontCollection * fontCollection,
         DWRITE_FONT_WEIGHT  fontWeight,
         DWRITE_FONT_STYLE  fontStyle,
         DWRITE_FONT_STRETCH  fontStretch,
         FLOAT  fontSize,
  [in]   const WCHAR * localeName,
  [out]  IDWriteTextFormat ** textFormat
)

fontFamilyName  -字体族名字

fontCollection - 字体集名字。

fontWeight - 字体突出程度

fontStyle - 字体风格

fontStretch - 字体拉伸,DWRITE_FONT_STRETCH 枚举值。

fontSize 字体大小

localeName - 指定字体语言

textFormat - 字体格式(返回值)

hr = DWriteFactory->CreateTextFormat(
    L"Script",
    NULL,
    DWRITE_FONT_WEIGHT_REGULAR,
    DWRITE_FONT_STYLE_NORMAL,
    DWRITE_FONT_STRETCH_NORMAL,
    24.0f,
    L"en-us",
    &TextFormat
    );

Font Alignment

先指定水平文本:使用IDWriteTextFormat::SetTextAlignment()函数。

参数为DWRITE_TEXT_ALIGNMENT枚举值

先后我们指定垂直位置。IDWriteTextFormat::SetParagraphAlignment()函数

参数为DWRITE_PARAGRAPH_ALIGNMENT枚举值

hr = TextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
hr = TextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);

我们这里把字体放在左上角

Keeping D3D 10.1 Debug Output Quiet ;)

尽管我们没有用D3D10.1来绘制东西,但是我们还是需要为D3D10.1来设置一些必要的步骤,以防止报错,

d3d101Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

Initializing the Scene to Display D2D

我们在这里创建一个square。并为之创建index,vertex buffer。然后为它创建一个shader resource,把D2D绘制的文本贴到square上

void InitD2DScreenTexture()
{
    //Create the vertex buffer
    Vertex v[] =
    {
        // Front Face
        Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f),
        Vertex(-1.0f,  1.0f, -1.0f, 0.0f, 0.0f),
        Vertex( 1.0f,  1.0f, -1.0f, 1.0f, 0.0f),
        Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f),
    };

    DWORD indices[] = {
        // Front Face
        0,  1,  2,
        0,  2,  3,
    };

    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = indices;
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &d2dIndexBuffer);


    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = v;
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &d2dVertBuffer);

    //Create A shader resource view from the texture D2D will render to,
    //So we can use it to texture a square which overlays our scene
    d3d11Device->CreateShaderResourceView(sharedTex11, NULL, &d2dTexture);
}

Modify the Blending Description

现在我们绘制的贴图除了字体的部分,都是不透明的。这会挡住我们后面的场景,现在我们需要设置blend state,使得文本的背景是透明的。

D3D11_RENDER_TARGET_BLEND_DESC rtbd;
ZeroMemory( &rtbd, sizeof(rtbd) );

rtbd.BlendEnable             = true;
rtbd.SrcBlend                 = D3D11_BLEND_SRC_COLOR;
///**************new**************
rtbd.DestBlend                 = D3D11_BLEND_INV_SRC_ALPHA;
///**************new**************
rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
rtbd.SrcBlendAlpha             = D3D11_BLEND_ONE;
rtbd.DestBlendAlpha             = D3D11_BLEND_ZERO;
rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

我们这里直接把DestBlend设置成D3D11_BLEND_INV_SRC_ALPHA

Clean Up

别忘了释放接口

void CleanUp()
{
    //Release the COM Objects we created
    SwapChain->Release();
    d3d11Device->Release();
    d3d11DevCon->Release();
    renderTargetView->Release();
    squareVertBuffer->Release();
    squareIndexBuffer->Release();
    VS->Release();
    PS->Release();
    VS_Buffer->Release();
    PS_Buffer->Release();
    vertLayout->Release();
    depthStencilView->Release();
    depthStencilBuffer->Release();
    cbPerObjectBuffer->Release();
    Transparency->Release();
    CCWcullMode->Release();
    CWcullMode->Release();

    ///**************new**************
    d3d101Device->Release();
    keyedMutex11->Release();
    keyedMutex10->Release();
    D2DRenderTarget->Release();    
    Brush->Release();
    BackBuffer11->Release();
    sharedTex11->Release();
    DWriteFactory->Release();
    TextFormat->Release();
    d2dTexture->Release();
    ///**************new**************
}

Rendering the Font

Let D3D 11 Release the Texture So D3D 10.1 Can Acquire It

//Release the D3D 11 Device
keyedMutex11->ReleaseSync(0);

//Use D3D10.1 device
keyedMutex10->AcquireSync(0, 5);    

保证两个D3D设备有效的交替使用texture

当D3D10.1获得使用权的时候,我们就调用D2D来绘制

//Draw D2D content        
D2DRenderTarget->BeginDraw();    

首先我们把贴图背景清除。

//Clear D2D Background
D2DRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));

Create the String (Text) That Will Be Rendered To the Screen

//Create our string
std::wostringstream printString; 
printString << text;
printText = printString.str();

输入显示字符

Set the D2D Brush Color

//Set the Font Color
D2D1_COLOR_F FontColor = D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f);
//Set the brush color D2D will use to draw with
Brush->SetColor(FontColor);    

Create a Rectangle That the Font Will Be Drawn In

创建一个矩形,来决定文本到底被绘制在屏幕的哪里

//Create the Text Render Area
D2D1_RECT_F layoutRect = D2D1::RectF(0, 0, Width, Height);

我们这里设置为全屏幕

Draw the Font

到这里,我们就可以绘制字体了。

通过函数ID2D1RenderTarget::DrawText():

void DrawText(
  [in]   WCHAR *string,
         UINT stringLength,
  [in]   IDWriteTextFormat *textFormat,
  [ref]  const D2D1_RECT_F &layoutRect,
  [in]   ID2D1Brush *defaultForegroundBrush,
         D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE,
         DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL
);

string - 文本内容

stringLength - 文本长度

textFormat - 这是DirectWirte指定的文本格式

layoutRect - 上一步中创建的文本矩形区域

defaultForegroundBrush - 指向画刷的指针

options - 一些额外的选项,指定文本是否被压缩成像素,或者当文本超出矩形时是否被裁剪。D2D1_DRAW_TEXT_OPTIONS 枚举值

measuringMode DWRITE_MEASURING_MODE 枚举值。

//Draw the Text
D2DRenderTarget->DrawText(
    printText.c_str(),
    wcslen(printText.c_str()),
    TextFormat,
    layoutRect,
    Brush
    );

Stop Drawing With D2D

D2DRenderTarget->EndDraw();    
//Release the D3D10.1 Device
keyedMutex10->ReleaseSync(1);

//Use the D3D11 Device
keyedMutex11->AcquireSync(1, 5);

Set the Blend State

我们要把放到square上的shader resource的文字alpha设为1,而其他位置被我们设置成了0(用clear方法)

这样,文字是可见的,而其他部分都是透明的。

Bind the Square to the IA

//Set the d2d Index buffer
d3d11DevCon->IASetIndexBuffer( d2dIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
//Set the d2d vertex buffer
UINT stride = sizeof( Vertex );
UINT offset = 0;
d3d11DevCon->IASetVertexBuffers( 0, 1, &d2dVertBuffer, &stride, &offset );

Render the Textured Square In "Screen Space"

WVP =  XMMatrixIdentity();
cbPerObj.WVP = XMMatrixTranspose(WVP);    
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );

Set the Shader Resource (Shared Texture) and Sampler State

d3d11DevCon->PSSetShaderResources( 0, 1, &d2dTexture );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

把绘制text的texture设置成square的纹理

Set the Render State, Then Draw the Square!

d3d11DevCon->RSSetState(CWcullMode);
//Draw the second cube
d3d11DevCon->DrawIndexed( 6, 0, 0 );    

The RenderScene() Function!

void DrawScene()
{
    //Clear our render target and depth/stencil view
    float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);    
    d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

    //Set our Render Target
    d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, depthStencilView );

    //Set the default blend state (no blending) for opaque objects
    d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff);

    ///**************new**************
    //Set the cubes index buffer
    d3d11DevCon->IASetIndexBuffer( squareIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    //Set the cubes vertex buffer
    UINT stride = sizeof( Vertex );
    UINT offset = 0;
    d3d11DevCon->IASetVertexBuffers( 0, 1, &squareVertBuffer, &stride, &offset );
    ///**************new**************

    //Set the WVP matrix and send it to the constant buffer in effect file
    WVP = cube1World * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 36, 0, 0 );

    WVP = cube2World * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 36, 0, 0 );

    ///**************new**************
    RenderText(L"Hello World");
    ///**************new**************

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}

讲到这里,这节内容终于讲完拉~ 

本节内容的代码部分可以在我的github里面找到!

游戏开发路途遥远,但我相信只要坚持,总能到达彼岸!

如果我的文章对于你学习DirectX11有点帮助,欢迎评论给出建议,让我们一起学习进步!

                                                                                               ———————— 小明 2018.12.8 14.33

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值