DX12 之 miniEngine 代码自解

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

好像有个叫 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

一些名词
环境光遮蔽

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖 • 3 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    59 引用 • 25 回帖 • 2 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    198 引用 • 543 回帖 • 3 关注
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    694 引用 • 537 回帖 • 5 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 185 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 521 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 615 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 1 关注
  • GitHub

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

    209 引用 • 2040 回帖
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    70 引用 • 193 回帖 • 409 关注
  • 招聘

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

    188 引用 • 1057 回帖
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    85 引用 • 324 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 12 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    42 引用 • 130 回帖 • 253 关注
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    29 引用 • 66 回帖 • 1 关注
  • Word
    13 引用 • 41 回帖
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 233 回帖 • 1 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 5 关注
  • 导航

    各种网址链接、内容导航。

    45 引用 • 177 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    20 引用 • 37 回帖 • 571 关注
  • 浅吟主题

    Jeffrey Chen 制作的思源笔记主题,项目仓库:https://github.com/TCOTC/Whisper

    1 引用 • 31 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖 • 1 关注
  • Git

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

    211 引用 • 358 回帖
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    6 引用 • 143 回帖