DX12 之 miniEngine 代码自解

本贴最后更新于 1924 天前,其中的信息可能已经时移俗易

好像有个叫 Vulkan 的图形 API 试图替代 Opengl,不过要跟上时代,还是得把 WIN10 装上,试试看在 DX12 写个下个时代的 3D 设计工具,当然只是个小玩意,不过据说很多软件能走到今天,也是因为积淀,如果不早点开始积淀,就永远会落后那么多
我想给这个玩意起个名字,叫啥呢,就叫 W4D 好了,首先这个软件定位不是游戏引擎,而且是超轻量的,W:windows 4:x y z t D: Data 指代在 windows 下处理 3 维与时间数据的软件
想要有什么呢?
1:首先需要能应对基本的 3D 建模,绑骨,展 UV 贴图,动作
2:要有语言能应对复杂的各种情况 类似 maxscript 脚本语言,但是这里可能级别要更强
3:最好有时间轴,觉得这个概念与 2D 里 flash 接近,跟 unity 里 animation 差不多,用不着搞成 timeline 那么复杂。
代码使用 C++ DX,确定要一切考虑效率,即使函数入口都不要检查,只在最外层进行检查,出错是程序员的事,代码不是用来为程序员负责的,希望这个玩意能像刀片一样 欻欻的就搞定哈哈

环境 WIN10,VS2017.这二者搞定后,微软官方例子可以直接打开编译运行的,miniEngine 可能要重定向项目-> 选择相应的 SDK 版本(在解决方案右键会出来选项)

dx12 基础
http://www.rastertek.com/tutdx12.html
官网的
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899121(v=vs.85).aspx

Working samples

例子
Working samples (in the form of Visual Studio 2015 projects) can be downloaded from GitHub/Microsoft/DirectX-Graphics-Samples.

DX12 遍历显卡
Intel(R) HD Graphics 4600
NVIDIA GeForce GTX 960M
Microsoft Basic Render Driver

把特性设置为 D3D_FEATURE_LEVEL_11_0

HRESULT hr = CreateDXGIFactory(__uuidof(IDXGIFactory4), (void**)&factory);
int i = 0;
IDXGIAdapter * pAdapter;
std::vector <IDXGIAdapter*> vAdapters;
while (factory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND)
{
//hr = D3D12CreateDevice(pAdapter, feature_level, __uuidof(ID3D12Device), (void**)& pDevice);
//if (SUCCEEDED(hr))
// break;
pAdapter->GetDesc(&adapterDesc);
wcstombs_s(&stringLength, (char*)videoCardDes, 128, adapterDesc.Description, 128);
vAdapters.push_back(pAdapter);
++i;
}

http://www.rastertek.com/
这个网站上 3 篇 DX12 的已经照着写了一遍,感觉概念多,复杂,勉强能跑个背景色出来就废了这么大功夫,可惜的是作者似乎没有继续写了
还是看 dx12 在 git 上的例子好了。

哎呀,感觉好难啊。
打开微软 ModelViewer_VS15.sln 的例子,运行效果还是挺好的,就是连入口都摸了半天
场景示例

入口是一个宏
CREATE_APPLICATION( ModelViewer )

映射到 GameCore::RunApplication( app_class(), L#app_class );
namespace GameCore 里定义了这个全局函数 RunApplication
这个函数初始化了引擎的各个部分,注册了窗口类,创建了消息循环,循环结束后,进行各个部分的资源清理,基本所有引擎框架都有类似这样的一个结构吧。
不过这个函数的入口是 c 语言的 int wmain(int argc, wchar_t** argv),估计是要通过宏适应多平台吧。

在 GameCore::RunApplication 里看下有哪些模块都在这里初始化的
在创建完窗口后调用了
InitializeApplication(app);
定义为
void InitializeApplication( IGameApp& game )
{
Graphics::Initialize();
SystemTime::Initialize();
GameInput::Initialize();
EngineTuning::Initialize();

game.Startup();

}
这个 game 参数,就是 ModelViewer 的一个对象

这部分基本和
www.rastertek.com
描述的差不多,图形初始化,输入初始化,游戏初始化,系统时间初始化,引擎协调初始化
Graphics::Initialize();在这里,
创建了 dx12 设备
(遍历了一次取了显存最大的显卡,还过滤掉了软件模拟卡)

SIZE_T MaxSize = 0;

for (uint32_t Idx = 0; DXGI_ERROR_NOT_FOUND != dxgiFactory->EnumAdapters1(Idx, &pAdapter); ++Idx)
{
DXGI_ADAPTER_DESC1 desc;
pAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;

if (desc.DedicatedVideoMemory > MaxSize && SUCCEEDED(D3D12CreateDevice(pAdapter.Get(), D3D_FEATURE_LEVEL_11_0, MY_IID_PPV_ARGS(&pDevice))))
{
	pAdapter->GetDesc1(&desc);
	Utility::Printf(L"D3D12-capable hardware found:  %s (%u MB)\n", desc.Description, desc.DedicatedVideoMemory >> 20);
	MaxSize = desc.DedicatedVideoMemory;
}

}

if (MaxSize > 0)
g_Device = pDevice.Detach();

然后检查了设备是否支持 R11G11B10 和 R16G16B16A16 2 种 UAVLoadSupport 上传格式,这部分仅仅设置了几个变量标识是否有此特性(g_bTypedUAVLoadSupport_R11G11B10_FLOAT,g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT)。

D3D12_FEATURE_DATA_D3D12_OPTIONS FeatureData = {};
if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &FeatureData, sizeof(FeatureData))))
{
if (FeatureData.TypedUAVLoadAdditionalFormats)
{
D3D12_FEATURE_DATA_FORMAT_SUPPORT Support =
{
DXGI_FORMAT_R11G11B10_FLOAT, D3D12_FORMAT_SUPPORT1_NONE, D3D12_FORMAT_SUPPORT2_NONE
};

	if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) &&
		(Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0)
	{
		g_bTypedUAVLoadSupport_R11G11B10_FLOAT = true;
	}

	Support.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;

	if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) &&
		(Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0)
	{
		g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT = true;
	}
}

}

接着创建了各种 commandqueue,
g_CommandManager.Create(g_Device);
g_CommandManager 是 CommandListManager 类型的
跟进去看一下
void CommandListManager::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);

m_Device = pDevice;

m_GraphicsQueue.Create(pDevice);
m_ComputeQueue.Create(pDevice);
m_CopyQueue.Create(pDevice);

}

m_GraphicsQueue m_ComputeQueue m_CopyQueue 都是 CommandQueue,这里的实现都一样

void CommandQueue::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);
ASSERT(!IsReady());
ASSERT(m_AllocatorPool.Size() == 0);

D3D12_COMMAND_QUEUE_DESC QueueDesc = {};
QueueDesc.Type = m_Type;
QueueDesc.NodeMask = 1;
pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue));
m_CommandQueue->SetName(L"CommandListManager::m_CommandQueue");

ASSERT_SUCCEEDED(pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, MY_IID_PPV_ARGS(&m_pFence)));
m_pFence->SetName(L"CommandListManager::m_pFence");
m_pFence->Signal((uint64_t)m_Type << 56);

m_FenceEventHandle = CreateEvent(nullptr, false, false, nullptr);
ASSERT(m_FenceEventHandle != INVALID_HANDLE_VALUE);

m_AllocatorPool.Create(pDevice);

ASSERT(IsReady());

}

这个类应该算对 dx12 commandqueue 的封装吧,核心就是使用 pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue));
然后创建了 fence,fence_event
m_AllocatorPool.Create(pDevice);只是把类的成员变量设置为 pDevice,这个 m_AllocatorPool 也是对 commandAllocator 的一个池的封装 他成员里有 std::vector<ID3D12CommandAllocator*> m_AllocatorPool;
这部分完了,返回堆栈上面的 Graphics::Initialize();

在 g_CommandManager.Create(g_Device);之后

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = g_DisplayWidth;
swapChainDesc.Height = g_DisplayHeight;
swapChainDesc.Format = SwapChainFormat;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) // Win32
ASSERT_SUCCEEDED(dxgiFactory->CreateSwapChainForHwnd(g_CommandManager.GetCommandQueue(), GameCore::g_hWnd, &swapChainDesc, nullptr, nullptr, &s_SwapChain1));

创建了一个交换链的描述,然后通过这个描述(swapChainDesc)创建了一个交换链,g_CommandManager.GetCommandQueue()返回的是他成员里,m_GraphicsQueue 创建的 CommandQueue, 交换链接口的指针放到 s_SwapChain1 里
下面有部分代码不明白意思,跳过去

#if CONDITIONALLY_ENABLE_HDR_OUTPUT && defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2)
{
IDXGISwapChain4* swapChain = (IDXGISwapChain4*)s_SwapChain1;
ComPtr output;
ComPtr output6;
DXGI_OUTPUT_DESC1 outputDesc;
UINT colorSpaceSupport;

// Query support for ST.2084 on the display and set the color space accordingly
if (SUCCEEDED(swapChain->GetContainingOutput(&output)) &&
	SUCCEEDED(output.As(&output6)) &&
	SUCCEEDED(output6->GetDesc1(&outputDesc)) &&
	outputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 &&
	SUCCEEDED(swapChain->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, &colorSpaceSupport)) &&
	(colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) &&
	SUCCEEDED(swapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)))
{
	g_bEnableHDROutput = true;
}

}
#endif
看英文解释是是否支持 ST.2048 并设置交换链的颜色空间的。

接着看
for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}

这部分是创建交换链使用的资源以及相应的 RenderTargetView,一个交换链上创建数个 RTV,话说这个包装还是有点难理解的。
交换链上 GetBuffer 类似按照指定的序号(在这里就是 i),把 ID3D12Resource 指针返回,要使用就创建一个 ID3D12Resource 描述的 RTV 对象
这个 RTV 对象是通过
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach())创建的
g_DisplayPlane 是一个 ColorBuffer 数组
跟进去看一下

void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);

//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
//Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]);

m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);

}

再跟进
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);

ColorBuffer 继承 PixelBuffer,跳转到这里

void PixelBuffer::AssociateWithResource( ID3D12Device* Device, const std::wstring& Name, ID3D12Resource* Resource, D3D12_RESOURCE_STATES CurrentState )
{
(Device); // Unused until we support multiple adapters

ASSERT(Resource != nullptr);
D3D12_RESOURCE_DESC ResourceDesc = Resource->GetDesc();

m_pResource.Attach(Resource);
m_UsageState = CurrentState;

m_Width = (uint32_t)ResourceDesc.Width;		// We don't care about large virtual textures yet
m_Height = ResourceDesc.Height;
m_ArraySize = ResourceDesc.DepthOrArraySize;
m_Format = ResourceDesc.Format;

#ifndef RELEASE
m_pResource->SetName(Name.c_str());
#else
(Name);
#endif
}

这个函数只是调用 ID312DResources 指针获取了资源描述 GetDesc(),然后把这些描述的属性记在了自己的成员里
跳出 AssociateWithResource 函数返回
void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);//跳出堆栈

//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
//Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]);
 //到这里
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);

}

m_RTVHandle 大概是一个 RTV 对象描述 的句柄。
跟进 Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);内部
inline D3D12_CPU_DESCRIPTOR_HANDLE AllocateDescriptor( D3D12_DESCRIPTOR_HEAP_TYPE Type, UINT Count = 1 )
{
return g_DescriptorAllocator[Type].Allocate(Count);
}
实际上最后是用 g_DescriptorAllocator[Type].Allocate(Count)返回了一个句柄,看下 g_DescriptorAllocator 定义
DescriptorAllocator g_DescriptorAllocator[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES] =
{
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
};
看的到这是一个 DescriptorAllocator 类型的数组,这个 g_DescriptorAllocator 数组有 4 个成员,每个成员都是一个
DescriptorAllocator 类型对象,这个类型对堆申请以及句柄申请做了一个池的封装

跟到 Allocate 里看一下
D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate( uint32_t Count )
{
if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count)
{
m_CurrentHeap = RequestNewHeap(m_Type);
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;

    if (m_DescriptorSize == 0)
        m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);
}

D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;
m_CurrentHandle.ptr += Count * m_DescriptorSize;
m_RemainingFreeHandles -= Count;
return ret;

}

没有堆就创建一个堆
跟到 m_CurrentHeap = RequestNewHeap(m_Type);这里
参数 m_Type 就跟 g_DescriptorAllocator[Type]的 Type 一样就是 D3D12_DESCRIPTOR_HEAP_TYPE_RTV

ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(D3D12_DESCRIPTOR_HEAP_TYPE Type)
{
std::lock_guard LockGuard(sm_AllocationMutex);

D3D12_DESCRIPTOR_HEAP_DESC Desc;
Desc.Type = Type;
Desc.NumDescriptors = sm_NumDescriptorsPerHeap;//256个描述
Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
Desc.NodeMask = 1;

Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap;
ASSERT_SUCCEEDED(Graphics::g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap)));
sm_DescriptorHeapPool.emplace_back(pHeap);
return pHeap.Get();

}

函数实现还带锁,那应该是提供给多线程使用的,这里也就是创建了一个堆描述对象,这个堆描述对象的 NumDescriptors 为 256(sm_NumDescriptorsPerHeap = 256)
最后还是通过
g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap);
创建了一个描述堆。
返回了创建的 ID3D12DescriptorHeap 指针
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
这个意思就是取得描述堆起始的句柄
if (m_DescriptorSize == 0)
m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);
这个就是取得每 2 个相邻句柄的指针要偏移多少

D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;
m_CurrentHandle.ptr += Count * m_DescriptorSize;
m_RemainingFreeHandles -= Count;
return ret;

这个就是说 Count 参数,申请多少个句柄,用 ret 存储返回的句柄,把原句柄按照数量偏移到新位置去。返回 ret(保存着偏移前的位置的句柄)

返回到
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
接着
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);
这句话意思类似:创建一个如 m_pResource(ID3D12Resource 指针)描述的资源对象,并把对象句柄存储在 m_RTVHandle 里,从此 m_RTVHandle 就真正指向了一个 RTV 对象
返回到堆栈上

for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}
循环里每一次运行,就申请了一个 RTVHandler,然后创建了一个 RTV 对象放到这个 RTVHandler 中,这个循环一共运行了 3 次(SWAP_CHAIN_BUFFER_COUNT = 3)

往下
太多代码还未理解。
觉得 dx12 还是需要先把概念扔出来,而不是这里这样那里那样,有了整体的概念,应该好理解很多

这里有一部分描述
https://www.csdn.net/article/a/2016-01-04/15833516

代码里有些类似乎涉及到一些算法,搜索了些资料保存下来慢慢看
双调排序 Bitonic Sort

一些名词
环境光遮蔽

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    75 引用 • 258 回帖 • 625 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    25 引用 • 217 回帖 • 166 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    9 引用 • 32 回帖 • 165 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 455 关注
  • danl
    64 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 425 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    106 引用 • 152 回帖
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • 博客

    记录并分享人生的经历。

    270 引用 • 2386 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖 • 2 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    189 引用 • 1056 回帖
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 54 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    7 引用 • 26 回帖 • 3 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 492 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    205 引用 • 357 回帖
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    82 引用 • 122 回帖 • 620 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    207 引用 • 2031 回帖
  • gRpc
    10 引用 • 8 回帖 • 56 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 25 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 25 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 23 关注
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 71 关注
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 626 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    533 引用 • 3528 回帖
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    90 引用 • 383 回帖 • 1 关注