4.1 기본기

4.1.1 Direct3D 12 개요

  • 정의 : 응용프로그램에서 GPU를 제어하고 프로그래밍 하는데 쓰는 저수준 그래픽 API
  • 책에서 "렌더 대상을 지운다" = 대상의 모든 원소를 특정한 하나의 값으로 설정한다. (delete, remove가 아님)
    • ID3D12CommandList::ClearRenderTargetView 메서드 호출
  • 응용 프로그램 - 그래픽 하드웨어 사이, Direct3D라는 간접층과 하드웨어 드라이버가 Direct3D 명령들을 기계어 명령들로 번역.
    • 응용 프로그램 개발자는 GPU 세부사항 걱정x
    • NIVIDIA, Intel, AMD 제조사들이 Direct3D의 명세를 준수하는 드라이버 제공 필요
  • D11과 차이점:
    • 새로운 렌더링 기능 추가
    • CPU 부담 감소 및 다중 스레드 지원
    • GPU에 더 가까운 수준의 API
    • 추상화 감소 및 개발자가 관리해야 할 사항 증가
    • 현세대 GPU 구조 밀접 반영
    • API 사용 난이도 증가 및 성능 개선
  • 여담 :
    • D12는 하드웨어 관리가 늘어나면서, D13 개발보다는 기능 추가가 되고 있음.

4.1.2 COM

  • COM (Component Object Model)
    • DirectX 프로그래밍 언어 독립성과 하위 호환성을 지원
    • c++로 DirectX 응용 프로그램을 프로그래밍 할 때, 'COM'의 세부 사항은 대부분 드러나지 않음
      • 'COM 인터페이스'를 new 키워드로 직접 생성할 일 없음
        • 프로그래머는 필요한 'COM 인터페이스'를 가리키는 포인터를 특별한 함수들을 이용 or
        • 다른 'COM'인터페이스의 메서드를 얻는 방법
    • 'COM 인터페이스'의 삭제는 delete가 아니라 인터페이스의 Release 메서드 호출
      • 모든 'COM 인터페이스'는 IUnknown 인터페이스 기능 상속, 해당 인터페이스는 Release 메서드 제공
      • 참조 횟수가 0이 되면 메모리에서 해제
    • 참고 : 'COM 인터페이스'의 이름은 대문자 I로 시작.
  • Microsoft::WRL::ComPtr <- "wrl.h" 필요
    • Com객체의 수명 관리를 돕는, Smart Pointer
    • 범위를 벗어난 ComPtr 인스턴스는 바탕 Com 객체에 대해 자동으로 Release 호출
      • (책에서 사용하는 메서드)
      • Get : 바탕 COM 인터페이스를 가리키는 포인터 반환.
          ComPtr<ID3D12RootSignature> mRootSignature;
          ...
          // SetGraphicsRootSignature는 ID3D12RootSignature* 형식의 인수를 받음
          mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
      • 해당 COM 인터페이스 포인터 형식의 인수를 받는 함수를 호출할 떄 사용
      • GetAddressOf : 바탕 COM 인터페이스를 가리키는 포인터의 주소를 반환.
        함수의 매개변수를 통해 포인터를 돌려받을 떄 흔히 사용
      • ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc; ... ThrowIfFailed(md3dDevice->CreateCommandAllocator( D3D12_COMMAND_LISTTYPE_DIRECT, mDirectCmdListAlloc.GetAddressOf()));
      • Reset: ComPtr : 인스터를 nullptr로 설정하고 바탕 COM 인터페이스의 참조 횟수를 1 감소
        • 직접 ComPtr 인스턴스에 nullptr 배정해도 됨
        • ComPtr<ID3D12Device> device = nullptr;

4.1.3 텍스쳐 형식

  • n차원 텍스쳐 = n차원 배열
  • 텍스쳐는 특정 형식의 자료 원소들만 담을 수 있음
    • DXGI_FORMAT 이라는 열거형으로 지정
      • DXGI_FORMAT_R32G32B32_FLAOT: 각 원소는 32bit 부동 소수점 성분 3 개로 구성
      • DXGI_FORMAT_R16G16B16A16_UNORM: 각 원소는 [0, 1] 구간으로 사상되는 16bit 성분 4 개로 구성
      • DXGI_FORMAT_R32G32_UINT: 각 원소는 부호없는 32bit 정수 성분 2 개로 구성
      • DXGI_FORMAT_R8G8B8A8_UNORM: 각 원소는 [0, 1] 구간으로 사상되는 부호 없는 8bit 성분 4 개로 구성
      • DXGI_FORMAT_R8G8B8A8_SNORM: 각 원소는 [-1, 1] 구간으로 사상되는 부호 있는 8bit 성분 4 개로 구성
      • DXGI_FORMAT_R8G8B8A8_SINT: 각 원소는 [-127, 127] 구간으로 사상되는 부호 있는 8bit 정수 성분 4 개로 구성
      • DXGI_FORMAT_R8G8B8A8_UINT: 각 원소는 [0, 255] 구간으로 사상되는 부호 없는 8bit 정수 성분 4 개로 구성
      • DXGI_FORMAT_R16G16B16A16_TYPELESS: 각 비트는 설정하지만, 타입을 지정하지 않음 <- 메모리만 확보, 추후 텍스처를 파이프라인에 묶을 때 지정하는 용도로 사용('c++'의 'reinterpret_cast'와 유사
        • reinterpret_cast : c++에서 다양한 타입 값 변환할 때 사용되는 연산자
      • -> DXGI_FORMAT_{RGBA_bit 표시}_{타입} 형태의 직관적 이름
      • -> [-127,1277], [0, 255]의 구간 차이로 인해, 색깔 값의 표현이 다소 달라짐

4.1.4 교환 사슬과 페이지 전환

  • 이중 버퍼링(Double Buffering) :
    • 후면 버퍼에서 그림을 그리고 전면 버퍼로 완성된 그림을 송출
    • 사용자는 완성된 그림 만을 보게 됨.
    • 버퍼 스왑(Buffer Swap) :
      • 후면 버퍼가 완전히 렌더링 되면, 후면 버퍼와 전면 버퍼의 역할이 서로 바뀜
      • 전면 버퍼와 후면 버퍼는 하나의 교환 사슬(swap chain)을 형성
        • IDXGISwapChain :
          • 전면 버퍼 픽셀 데이터와 후면 버퍼 텍스처를 담음
          • IDXGISwapChain::ResizeBuffers : 버퍼 크기 변경
          • IDXGISwapChain::Present : 버퍼의 *"제시"*
      • D3D에서는 제시(presenting) 라고 부름
    • 전면 버퍼(Front Buffer) :
      • 저장된 내용 표시
      • 픽셀(Pixel) 단위 데이터 처리
    • 후면 버퍼 (Back Buffer) :
      • 그래픽 데이터가 먼저 렌더링 되는 곳
      • 그래픽 연산 수행
      • 현재 화면에 표시x
      • 텍셀(Texel) 단위 데이터 처리
        • 하지만, 픽셀 데이터 저장이 주된 역할
    • 장점 :
      • 화면 깜빡임 방지: 그래픽 데이터를 렌더링하는 동안 깜박임 현상 방지
      • 티어링 방지: 화면이 잘못된 위치에서 찢어지는 티어링 방지
         -> 프레임 버퍼와 모니터 동기화에서 발생하는 문제, 이전 프레임과 섞이는 현상으로 수직, 수평 방향으로 어긋남 발생; (정점 버퍼와 관계 x)

4.1.5 깊이 버퍼링

  • 깊이 버퍼(Detph Buffer) :
    • 이미지 자료를 담지 않는 텍스처
    • 각 픽셀의 깊이 정보 보관
      • 깊이는 0..0 ~ 1.0
        • 0.0 : 시야 절두체 안에서 관찰자와 최대한 가까운 물체
        • 1.0 : 시야 절두체 안에서 관찰자와 최대한 먼 물체
        • 시야 절두체(view frustum)
          • 3D 그래픽스에서 카메라가 볼 수 있는 공간을 정의
          • 구성
            • 근평면 (Near Plane): 카메라와 가장 가까운 절두체의 면으로, 화면에 표시되는 최소 거리입니다.
            • 원평면 (Far Plane): 카메라와 가장 먼 절두체의 면으로, 화면에 표시되는 최대 거리입니다.
            • 상면 (Top Plane): 절두체의 윗면으로, 카메라의 시야 각을 정의합니다.
            • 하면 (Bottom Plane): 절두체의 아랫면으로, 카메라의 시야 각을 정의합니다.
            • 좌면 (Left Plane): 절두체의 왼쪽 면으로, 카메라의 시야 각을 정의합니다.
            • 우면 (Right Plane): 절두체의 오른쪽 면으로, 카메라의 시야 각을 정의합니다.
    • 깊이 버퍼의 원소들과 후면 버퍼의 픽셀들은 일대일 대응
      • ex) 1080 x 1024 해상도는 1280 x 1024개 원소 깊이 버퍼로 구성
    • 깊이 버퍼는 하나의 텍스처
      • 생성 시 특정한 자료 원소 형식 지정 필요
      • ex)
        • DXGI_FORMAT_D32_FLOAT_S8X24_UINT : 각 텍셀은 32비트 부동소수점 깊이 값과 [0,255] 구간으로 사상되는 부호 없는 8bit 정수 스텐실 값, 그리고 다른 용도 없이 채움(padding)용 24bit 구성
        • DXGI_FORMAT_D32_FLOAT : 각 텍셀은 32비트 부동소수점 깊이 값
        • DXGI_FORMAT_D24_FLOAT_S8_UINT : 각 텍셀은 [0,1]구간으로 사상되는 부호 없는 24bit 깊이 값 1개, [0,255]구간 사상되는 부호 없는 8bit 정수 스텐실 값
        • DXGI_FORMAT_D16_UNORM : 각 텍셀은 [0,1] 구간으로 사상되는 부호 없는 16bit 깊이 값
          • -> 응용 프로그램이 스텐실 버퍼를 반드시 사용해야 하는 것은 아니나, 사용한다면 스텐실 버퍼는 항상 깊이 버퍼와 같은 텍스처에 포함
  • "D3D"는 깊이 버퍼링 또는 z-버퍼링 사용
    • 깊이 버퍼링 : 그리는 순서 무관하게 물체들이 제대로 가려짐
      • (z-버퍼링과 깊이 버퍼링은 같은 개념 지칭)
    • 깊이 판정 실패 시, 버퍼 갱신 x
      • 가장 앞에 있는 물체보다 뒤에 물체가 추가 될 떄, 버퍼 갱신을 하지않음

4.1.6 자원과 서술자

  • 그리기 호출
    • 호출 전에 필요한 자원을 바인딩 시킬 필요가 있다
    • 필요하다면 호출마다 바인딩 갱신
    • GPU 자원들이 파이프 라인에 직접 묶이는 것 X
      • 자원 서술자를 통해 실제 자료 접근
    • 호출이 참조할 서술자 명시 -> 해당 자원들이 렌더링 파이프라인에 바인딩
  • 서술자(Descriptor) : { <- "View"는 서술자와 동의어 }
    • 자원을 GPU에게 서술해 주는 경량 자료구조
    • 서술자는 간접층(level of indirection)
      • GPU 자원이 사실상 범용적인 메모리 조각이기 때문
    • 응용프로그램 초기화 시점에 생성해야 함
      • 해당 시점에 일정 정도의 형식 점검과 유효성 검증이 일어나기 떄문
      • 초기화 시점에서 생성하는 것이 실행 시점보다 나음
        • 최적화, 안정성(디버깅), 반복 재사용, 메모리관리, 서술자 힙 구조 적합성 등으로 인해
    • 서술자 종류 일부 (책에 사용하는 것들)
      • Shader Resource View(SRV) : 텍스처의 특정 MIP 레벨이나 배열의 특정 슬라이스를 참조
      • Constant Buffer View (CBV) : 상수 버퍼의 특정 범위를 참조
      • Unordered Access View (UAV) : 버퍼나 텍스처의 특정 영역에 쓰기 작업 수행
      • 표본추출기 서술자 : 텍스처 적용에 쓰이는 표본추출기(sampler) 자원 서술
      • Render Target View(RTV) : 렌더 대상 자원 서술
      • Depth Stencil View(DSV) : 깊이/스텐실 자원 서술
    • 자원
      • 범용적
      • 같은 자원을 렌더링 파이프라인의 서로 다른 단계(stage)에 사용 가능
        • ex) 텍스처 : 렌더 대상 사용 -> 셰이더 자원으로도 사용
        • 단계마다 개별적인 서술자 필요
      • 사용처에 대한 명시 x
        • 서술자가 지정 및 GPU에 서술해줄 필요가 있음
      • 부분 영역 정보 x
        • 자원의 일부 영역만 렌더링 파이프라인에 묶이는게 불가
        • 서술자가 부분 영역을 지정해줄 수 있음
          • SRV, CBV, UAV
      • 무형식으로 생성 가능
        • GPU는 자원 형식 파악 불가
        • 해당 자원을 참조하는 서술자를 생성할 떄, 구체적인 형식 명시 가능
        • 런탐임에 자원에 대한 접근 최적화를 위해, 가능한 지양
  • 서술자 힙(Descriptor heap)
    • 서술자 배열
    • 서술자 종류마다 개별적인 힙 필요
      • 같은 종류는 같은 힙에 저장 ( 한 종류에 여러개 힙도 가능 )

4.1.7 다중표본화의 이론

  • 앨리어싱 제거(antialiasing) 기법
    • 초과표본화(supersampling) :
      • 후면 버퍼와 깊이 버퍼를 화면 해상도보다 4배 (가로 2, 세로2) 크게 잡고, 렌더링. -> 제시할 떄, 버퍼를 원래 크기로 환원(resolving)
        • 하양표본화(downsampling) :
          • 4pixel 블록의 네 생상의 평균을 최종 색상으로 지정하는 환원 공정
          • 소프트웨어에서 해상도를 늘려 해결하는 방식
      • 4배로 늘렸기에 비용이 높음
    • 다중표본화(multisampling)
      • 절충적인 앨리어싱 제거 기법 (D3D에서 사용)
      • 일부 계산 결과를 부분픽셀(subpixel)들 사이서 공유 -> 비용 저렴
      • supersampling 처럼 x배인 후면 버퍼와 깊이 버퍼 사용
        • 픽셀당 한 번만 색상 계산 -> 해당 색상과 부분픽셀들의 가시성, 포괄도를 이용해 최종 결정

4.1.8 Direct3D의 Multisampling

  • DXGI_SAMPLE_DESC 구조체를 적절히 채워야 함
    •     typedef struct DXGI_SAMPLE_DESC
          {
              UINT Count;      // 추출할 표본 개수
              UINT Quality     // 원하는 품질 수준
          } DXGI_SAMPLE_DESC;
    • 표본_개수&품질_수준은 "렌더링 비용"과 정비례
    • 교환 사슬 버퍼, 깊이 버퍼 모두 필요
    • 후면 버퍼와 깊이 버퍼를 생성할 떄, 동일한 multisampling 설정 적용
  • 텍스처 형식과 표본 개수이 조합에 대한 품질 수준 개수 파악 메서드 사용 예
      typedef struct D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS {
          DXGI_FORMAT            
          Format;
          UINT                    
          SampleCount;
          D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG Flags;
          UINT                    
          NumQualityLevels;
      } D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS;
    
      D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
      msQualityLevels.Format = mBackBufferFormat;
      msQualityLevels.SampleCount = 4;
      msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
      msQualityLevels.NumQualityLevels = 0;
      ThrowIfFailed(md3dDevice->CheckFeatureSupport(
          D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
          &msQualityLevels, sizeof(msQualityLevels)));
    ```</div></details>
  • 실제 응용에서 multisampling 표본을 4~8개만 추출
  • D3D12 대응 장치는 모든 렌더 대상 형식에 대해 4x multisampling 지원
    • Count 1, Quality 0 설정하면 multisampling 사용 x

4.1.9 기능수준

  • D3D_FEATURE_LEVEL 열거형
    • GPU가 지원하는 기능들의 엄격한 집합

4.1.10 DXGI (DirectX Graphics Infrastructure)

  • D3D와 함께 쓰이는 API
    • IDXGISwapChain
    • 화면 전환 등등
    • <- IDXGI{Interface}{Ver} 을 쓰기 위해서는 `<dxgi{Ver}>` 헤더 필요 <- 헤더 링크 설정 필요 `#pragma comment(lib, "dxgi.lib"`>
  • IDXGIFactory : DXGI를 사용하기 위해 필요한 각종 인터페이스 생성
  • IDXGIAdapter : 그래픽카드 열거
  • IDXGIOutput : 그래픽 카드에 연결된 디스플레이 기능 제어
  • IDXGISwapChain : double buffering, triple buffering 에 사용
    • 시스템 모든 디스플레이 어댑터 열거
        //시스템의 모든 디스플레이 어댑터를 열거한다.
        void D3DApp::LogAdapters()
        {
          // DXGI Factory 생성
          ComPtr<IDXGIFactory4> mdxgiFactory;
          if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory))))
                  return -1;
      
            UINT i = 0;
            IDXGIAdapter*  adapter = nullptr;
            std::vector<IDXGIAdapter*> adapterList;
            while (mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
            {
                DXGI_ADAPTER_DESC desc;
                adapter->GetDesc(&desc);
      
                std::wstring text = L"***Adapter";
                text += desc.Description;
                text+= L'\n';
      
                OutputDebugString(text.c_str());
      
                adapterList.push_back(adapter);
      
                ++i;
            }
      
            for (size_t i = 0; i < adapterList.size(); ++i)
            {
                LogAdapterOutputs(adapterList[i]);
            }
        }
      ```</div></details>
    • 어댑터 관련 모든 출력 열거
        //주어진 어댑터와 연관된 모든 출력을 열거한다.
        void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
        {
            UINT i = 0;
            IDXGIOutput* output = nullptr;
            while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
            {
                DXGI_OUTPUT_DESC desc;
                output->GetDesc(&desc);
      
                std::wstring text = L"***Output : ";
                text += desc.DeviceName;
                text += L"\n";
                OutputDebugString(text.c_str());
      
                LogOutputDisplayModes(output, mBackBufferFormat);
      
                ReleaseCom(output);
      
                ++i;
            }
        }
      ```</div></details>
    • DXGI_MODE_DESC 해당 형태 구조체들은 디스플레이 관련 정보를 담는다.
    • tytypedef struct DXGI_MODE_DESC { UINT Width; // 가로 해상도 UINT Height; // 세로 해상도 DXGI_RATIONAL RefreshRate; // 주사율 DXGI_FORMAT Format; // 디스플레이 형식 DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; //스캔 방식 : 순차 주사(프로그레시브) or 비월 주사(인터레이스) DXGI_MODE_SCALING Scaling; // 영상 모니터 크기에 맞게 스케일 값 } ```
    • 주어진 출력과 디스플레이 형식을 지원하는 모든 디스플레이 모드 목록 출력

      ```
      주어진 출력과 픽셀 형식의 조합이 지원하는 모든 디스플레이 모드를 나열한다.
      void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
      {

        UINT count = 0;
        UINT flags = 0;
      
        //nullpter을 인수로 해서 호출하면 목록의 크기(모드 개수)를 얻게 된다.
        output->GetDisplayModeList(format, flags, &count, nullptr);
      
        std::vector<DXGI_MODE_DESC> modeList(count);
        output->GetDisplayModeList(format, flags, &count, &modeList[0]);
      
        for (auto& x : modeList)
        {
            UINT n = x.RefreshRate.Numerator;
            UINT d = x.RefreshRate.Denominator;
            std::wstring text =
                L"Width = " + std::to_wstring(x.Width) + L" " +
                L"Height = " + std::to_wstring(x.Height) + L" " +
                L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
                L"\n";
      
            ::OutputDebugString(text.c_str());
      
        }

      }
      ```

      • 전체 화면 성능 최적화를 위해 지원 모니터 지정에 유용
    • 위의 예제 코드 실행 시, 필요 전처리문
                  #pragma comment(lib, "dxgi.lib")
      
                  #include <dxgi1_4.h>// IDXGI_
                  #include <wrl.h>    // Microsoft::WRL::ComPtr
                  #include <vector>
                  #include <iostream>
                  #include <string>    // std::to_string
      
                  #ifndef ReleaseCom
                  #define ReleaseCom(x) { if(x){ x->Release(); x = 0; } }
                  #endif
      ```</div></details>
      
      

4.1.11 기능지원점검

  • CheckFeatureSupport
  • HRHRESULT ID3D12Device::CheckFeatureSupport( D3D12_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize );
  • Feature : 지원 여부 점검할 기능 종류
    • D3D12_FEATURE_D3D12_OPTIONS : D3D 12의 여러 기능
    • D3D12_FEATURE_ARCHITECTURE : 하드웨어 아키텍처 기능들
    • D3D12_FEATURE_FEATURE_LEVELS : 기능 수준들
    • D3D12_FEATURE_FORMAT_SUPPORT : 텍스처 형식에 대한 기능
    • D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS : 다중 표본화 기능
  • pFeatureSupportData : 기능 지원 정보 설정될 구조체 포인터
    • Feature 의 타입과 동일해야 함
  • FeatureSupportDataSize : pFeatureSupportData 에 전달한 구조체 크기
  • 다중 기능 지원 점검 예제
      typedef struct D3D12_FEATURE_DATA_FEATURE_LEVELS 
      {
              UINT NumFeatureLevels;
              const D3D_FEATURE_LEVEL
              *pFeatureLevelsRequested;
              D3D_FEATURE_LEVEL   MaxSupportedFeatureLevel; 
          } D3D12_FEATURE_DATA_FEATURE_LEVELS;
          D3D_FEATURE_LEVEL featureLevels[3] =
      {
          D3D_FEATURE_LEVEL_11_0, // First check D3D 11 support
          D3D_FEATURE_LEVEL_10_0, // Next, check D3D 10 support
          D3D_FEATURE_LEVEL_9_3  // Finally, check D3D 9.3 support
      };
      D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevelsInfo;
      featureLevelsInfo.NumFeatureLevels = 3;
      featureLevelsInfo.pFeatureLevelsRequested = featureLevels;
      md3dDevice->CheckFeatureSupport(
          D3D12_FEATURE_FEATURE_LEVELS,
          &featureLevelsInfo,
          sizeof(featureLevelsInfo));
    ```</div></details>
    
    

4.1.12 상주성

  • 상주성(regsidency) :
    • 응용 프로그램 자원을 GPU메모리에 내리('퇴거')거나 올림('입주')으로 자원의 상주성을 관리
    • GPU 메모리 양 최소화가 핵심
    • 상주성 변경의 적합한 시점 예 : "게임 레벨", "게임 지역" 변화 시점
    • 기본적으로 자원 생성 -> 입주, 파괴 -> 퇴거
    • MakeResident : 자원을 GPU 메모리에 유지
        HRESULT MakeResident(
        UINT NumObjects,
        ID3D12Pageable *const *ppObjects
      ));
    • Evict : 자원을 GPU 메모리에서 내보냄
        HRESULT Evit(
            UINT NumObjects,
            ID3D12Pageable *const *ppObjects
        );

'프레임워크 > DirectX' 카테고리의 다른 글

Direct3D의 초기화_게임프레임워크&타이머 코드 의문점  (0) 2025.03.07
04.Direct3D의_초기화03  (1) 2025.03.04
04.Direct3D의_초기화02  (0) 2025.03.04
02.Matrix  (0) 2025.02.25
01.Vector  (0) 2025.02.25

+ Recent posts

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });